]> git.parisson.com Git - django-social-auth.git/commitdiff
Added backends module with contribs section and simpler way to add extra backends...
authorMatías Aguirre <matiasaguirre@gmail.com>
Tue, 18 Jan 2011 17:14:54 +0000 (15:14 -0200)
committerMatías Aguirre <matiasaguirre@gmail.com>
Tue, 18 Jan 2011 17:14:54 +0000 (15:14 -0200)
20 files changed:
README.rst
example/settings.py
example/templates/done.html
example/templates/home.html
social_auth/__init__.py
social_auth/admin.py
social_auth/auth.py [deleted file]
social_auth/backends.py [deleted file]
social_auth/backends/__init__.py [new file with mode: 0644]
social_auth/backends/contrib/__init__.py [new file with mode: 0644]
social_auth/backends/contrib/livejournal.py [new file with mode: 0644]
social_auth/backends/contrib/orkut.py [new file with mode: 0644]
social_auth/backends/facebook.py [new file with mode: 0644]
social_auth/backends/google.py [new file with mode: 0644]
social_auth/backends/twitter.py [new file with mode: 0644]
social_auth/backends/yahoo.py [new file with mode: 0644]
social_auth/conf.py [deleted file]
social_auth/store.py
social_auth/urls.py
social_auth/views.py

index 6f066000e6f2a8fc82e48c8b8907173144727272..b55d941f3b8632223d1f875ad26e45cf7637d4cb 100644 (file)
@@ -29,10 +29,13 @@ credentials, some features are:
     * `Google OpenID`_
     * `Google OAuth`_
     * `Yahoo OpenID`_
-    * `LiveJournal OpenID`_
     * OpenId_ like myOpenID_
     * `Twitter OAuth`_
     * `Facebook OAuth`_
+
+  Some contributions added support for:
+
+    * `LiveJournal OpenID`_
     * `Orkut OAuth`_
 
 - Basic user data population and signaling, to allows custom fields values
@@ -95,26 +98,43 @@ Configuration
 - Add desired authentication backends to AUTHENTICATION_BACKENDS_ setting::
 
     AUTHENTICATION_BACKENDS = (
-        'social_auth.backends.TwitterBackend',
-        'social_auth.backends.FacebookBackend',
-        'social_auth.backends.OrkutBackend',
-        'social_auth.backends.GoogleOAuthBackend',
-        'social_auth.backends.GoogleBackend',
-        'social_auth.backends.YahooBackend',
-        'social_auth.backends.LiveJournalBackend',
+        'social_auth.backends.twitter.TwitterBackend',
+        'social_auth.backends.facebook.FacebookBackend',
+        'social_auth.backends.google.GoogleOAuthBackend',
+        'social_auth.backends.google.GoogleBackend',
+        'social_auth.backends.yahoo.YahooBackend',
+        'social_auth.backends.contrib.LiveJournalBackend',
+        'social_auth.backends.contrib.orkut.OrkutBackend',
         'social_auth.backends.OpenIDBackend',
         'django.contrib.auth.backends.ModelBackend',
     )
 
+  Note: this was introduced in a recent change and it's not backward
+  compatible, take into account that saved sessions won't be able to login
+  because the backend string stored in session (like backends.TwitterBackend)
+  won't match the new paths.
+
+- The app will try to import custom backends from the sources defined in::
+
+    SOCIAL_AUTH_IMPORT_BACKENDS = (
+        'myproy.social_auth_extra_services',
+    )
+
+  This way it's easier to add new providers, check the already defined ones
+  in social_auth.backends for examples.
+
+  Take into account that backends must be defined in AUTHENTICATION_BACKENDS_
+  or Django won't pick them when trying to authenticate the user.
+
 - Setup Twitter, Facebook, Orkut and Google OAuth keys (see OAuth_ section
   for details)::
 
-    TWITTER_CONSUMER_KEY    = ''
-    TWITTER_CONSUMER_SECRET = ''
-    FACEBOOK_APP_ID         = ''
-    FACEBOOK_API_SECRET     = ''
-    ORKUT_CONSUMER_KEY      = ''
-    ORKUT_CONSUMER_SECRET   = ''
+    TWITTER_CONSUMER_KEY     = ''
+    TWITTER_CONSUMER_SECRET  = ''
+    FACEBOOK_APP_ID          = ''
+    FACEBOOK_API_SECRET      = ''
+    ORKUT_CONSUMER_KEY       = ''
+    ORKUT_CONSUMER_SECRET    = ''
     GOOGLE_CONSUMER_KEY      = ''
     GOOGLE_CONSUMER_SECRET   = ''
 
index 8ea77709b8c663a90ab3243d35c3150e9709d16d..f2e7d7c8d733d2220adffb8083a700b1856507a4 100644 (file)
@@ -62,13 +62,13 @@ INSTALLED_APPS = (
 )
 
 AUTHENTICATION_BACKENDS = (
-    'social_auth.backends.TwitterBackend',
-    'social_auth.backends.FacebookBackend',
-    'social_auth.backends.GoogleOAuthBackend',
-    'social_auth.backends.GoogleBackend',
-    'social_auth.backends.YahooBackend',
+    'social_auth.backends.twitter.TwitterBackend',
+    'social_auth.backends.facebook.FacebookBackend',
+    'social_auth.backends.google.GoogleOAuthBackend',
+    'social_auth.backends.google.GoogleBackend',
+    'social_auth.backends.yahoo.YahooBackend',
     'social_auth.backends.OpenIDBackend',
-    'social_auth.backends.LiveJournalBackend',
+    'social_auth.backends.contrib.livejournal.LiveJournalBackend',
     'django.contrib.auth.backends.ModelBackend',
 )
 
index a558549bd4d7fc5cb298451dda825c7c7dbc23e7..3b90b81e3129ccac8ff0347ad7cd5fc791b92c7c 100644 (file)
       <a rel="nofollow" href="/associate/yahoo/">Yahoo</a>
       {% if yahoo %}<span class="associated">(associated)</span>{% endif %}
     </li>
-
+    <li>
+      <form action="/login/livejournal/" method="post">{% csrf_token %}
+        <div>
+          <label for="openid_lj_user">LiveJournal user
+            {% if livejournal %} <span class="associated">(associated)</span>{% endif %}:
+          </label>
+          <input id="openid_lj_user" type="text" value="" name="openid_lj_user" />
+          <input type="submit" value="Login"/>
+        </div>
+      </form>
+    </li>
     <li>
       <form action="/associate/openid/" method="post">{% csrf_token %}
         <div>
index f54d252e07657b9078607930f5a5da0669ccbb9c..636d2782c62cb2b80ce811fbd6a192171044849c 100644 (file)
   <ul>
     <li><a rel="nofollow" href="/login/google/">Google</a></li>
     <li><a rel="nofollow" href="/login/yahoo/">Yahoo</a></li>
-       <li>
-         <form action="/login/livejournal/" method="post">{% csrf_token %}
-           <div>
+    <li>
+      <form action="/login/livejournal/" method="post">{% csrf_token %}
+        <div>
           <label for="openid_lj_user">LiveJournal user:</label>
           <input id="openid_lj_user" type="text" value="" name="openid_lj_user" />
           <input type="submit" value="Login"/>
-               </div>
+        </div>
       </form>
     </li>
     <li>
index bcf6328db8f6b82bb1690bf6ec6d31f5e3d92f91..13d2ab156660496f099106c1d6deec4b6929cbfd 100644 (file)
@@ -2,5 +2,5 @@
 Django-social-auth application, allows OpenId or OAuth user
 registration/authentication just adding a few configurations.
 """
-version = (0, 1, 7)
+version = (0, 2, 0)
 __version__ = '.'.join(map(str, version))
index d8dde01dd4da7bfede4b7546a0a672bd65b510f1..cd1a60a49591723cc61ba638f931b0278deedb49 100644 (file)
@@ -1,7 +1,7 @@
 """Admin settings"""
 from django.contrib import admin
 
-from .models import UserSocialAuth, Nonce, Association
+from social_auth.models import UserSocialAuth, Nonce, Association
 
 
 class UserSocialAuthOption(admin.ModelAdmin):
diff --git a/social_auth/auth.py b/social_auth/auth.py
deleted file mode 100644 (file)
index 0515a6e..0000000
+++ /dev/null
@@ -1,471 +0,0 @@
-"""Authentication handling class"""
-import cgi
-import urllib
-import urllib2
-import httplib
-
-from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE
-from openid.consumer.discover import DiscoveryFailure
-from openid.extensions import sreg, ax
-from oauth.oauth import OAuthConsumer, OAuthToken, OAuthRequest, \
-                        OAuthSignatureMethod_HMAC_SHA1
-
-from django.conf import settings
-from django.utils import simplejson
-from django.contrib.auth import authenticate
-
-from .store import DjangoOpenIDStore
-from .backends import TwitterBackend, OrkutBackend, FacebookBackend, \
-                      OpenIDBackend, GoogleBackend, YahooBackend, \
-                      GoogleOAuthBackend, LiveJournalBackend
-from .conf import AX_ATTRS, SREG_ATTR, OPENID_ID_FIELD, SESSION_NAME, \
-                  OPENID_GOOGLE_URL, OPENID_YAHOO_URL, TWITTER_SERVER, \
-                  OPENID_LIVEJOURNAL_URL, OPENID_LIVEJOURNAL_USER_FIELD, \
-                  TWITTER_REQUEST_TOKEN_URL, TWITTER_ACCESS_TOKEN_URL, \
-                  TWITTER_AUTHORIZATION_URL, TWITTER_CHECK_AUTH, \
-                  FACEBOOK_CHECK_AUTH, FACEBOOK_AUTHORIZATION_URL, \
-                  FACEBOOK_ACCESS_TOKEN_URL, GOOGLE_REQUEST_TOKEN_URL, \
-                  GOOGLE_ACCESS_TOKEN_URL, GOOGLE_AUTHORIZATION_URL, \
-                  GOOGLE_SERVER, GOOGLE_OAUTH_SCOPE, GOOGLEAPIS_EMAIL, \
-                  ORKUT_REST_ENDPOINT, ORKUT_DEFAULT_DATA, ORKUT_SCOPE
-
-
-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, *args, **kwargs):
-        """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 OpenIdAuth(BaseAuth):
-    """
-    OpenId process handling
-        @AUTH_BACKEND   Authorization backend related with this service
-    """
-    AUTH_BACKEND = OpenIDBackend
-
-    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, *args, **kwargs):
-        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:
-            kwargs.update({'response': response, self.AUTH_BACKEND.name: True})
-            return authenticate(*args, **kwargs)
-        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):
-        """Setup request"""
-        openid_request = self.openid_request()
-        # Request some user details. Use attribute exchange if provider
-        # advertises support.
-        if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri):
-            fetch_request = ax.FetchRequest()
-            # Mark all attributes as required, 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):
-        """Return true if openid request will be handled with redirect or
-        HTML content will be returned.
-        """
-        if not hasattr(self, '_uses_redirect'):
-            setattr(self, '_uses_redirect',
-                    self.openid_request().shouldSendRedirect())
-        return getattr(self, '_uses_redirect', True)
-
-    def openid_request(self):
-        """Return openid request"""
-        if not hasattr(self, '_openid_request'):
-            openid_url = self.openid_url()
-            try:
-                openid_request = self.consumer().begin(openid_url)
-            except DiscoveryFailure, err:
-                raise ValueError('OpenID discovery error: %s' % err)
-            else:
-                setattr(self, '_openid_request', openid_request)
-        return getattr(self, '_openid_request', None)
-
-    def openid_url(self):
-        """Return service provider URL.
-        This base class is generic accepting a POST parameter that specifies
-        provider URL."""
-        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"""
-    AUTH_BACKEND = GoogleBackend
-
-    def openid_url(self):
-        """Return Google OpenID service url"""
-        return OPENID_GOOGLE_URL
-
-
-class YahooAuth(OpenIdAuth):
-    """Yahoo OpenID authentication"""
-    AUTH_BACKEND = YahooBackend
-
-    def openid_url(self):
-        """Return Yahoo OpenID service url"""
-        return OPENID_YAHOO_URL
-
-
-class LiveJournalAuth(OpenIdAuth):
-    """LiveJournal OpenID authentication"""
-    AUTH_BACKEND = LiveJournalBackend
-
-    def uses_redirect(self):
-        """LiveJournal uses redirect"""
-        return True
-
-    def openid_url(self):
-        """Returns LiveJournal authentication URL"""
-        if self.request.method != 'POST' or \
-           not self.request.POST.get(OPENID_LIVEJOURNAL_USER_FIELD):
-            raise ValueError, 'Missing LiveJournal user identifier'
-        return OPENID_LIVEJOURNAL_URL % \
-                    self.request.POST[OPENID_LIVEJOURNAL_USER_FIELD]
-
-
-class BaseOAuth(BaseAuth):
-    """OAuth base class"""
-    def __init__(self, request, redirect):
-        """Init method"""
-        super(BaseOAuth, self).__init__(request, redirect)
-        self.redirect_uri = self.request.build_absolute_uri(self.redirect)
-
-
-class ConsumerBasedOAuth(BaseOAuth):
-    """Consumer based mechanism OAuth authentication, fill the needed
-    parameters to communicate properly with authentication service.
-
-        @AUTHORIZATION_URL       Authorization service url
-        @REQUEST_TOKEN_URL       Request token URL
-        @ACCESS_TOKEN_URL        Access token URL
-        @SERVER_URL              Authorization server URL
-        @AUTH_BACKEND            Authorization backend related with
-                                 this service
-    """
-    AUTHORIZATION_URL = ''
-    REQUEST_TOKEN_URL = ''
-    ACCESS_TOKEN_URL = ''
-    SERVER_URL = ''
-    AUTH_BACKEND = None
-
-    def auth_url(self):
-        """Returns redirect url"""
-        token = self.unauthorized_token()
-        name = self.AUTH_BACKEND.name + 'unauthorized_token_name'
-        self.request.session[name] = token.to_string()
-        return self.oauth_request(token, self.AUTHORIZATION_URL).to_url()
-
-    def auth_complete(self, *args, **kwargs):
-        """Returns user, might be logged in"""
-        name = self.AUTH_BACKEND.name + 'unauthorized_token_name'
-        unauthed_token = self.request.session.get(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()
-
-        kwargs.update({'response': data, self.AUTH_BACKEND.name: True})
-        return authenticate(*args, **kwargs)
-
-    def unauthorized_token(self):
-        """Return request for unauthorized token (first stage)"""
-        request = self.oauth_request(token=None, url=self.REQUEST_TOKEN_URL)
-        response = self.fetch_response(request)
-        return OAuthToken.from_string(response)
-
-    def oauth_request(self, token, url, extra_params=None):
-        """Generate OAuth request, setups callback url"""
-        params = {'oauth_callback': self.redirect_uri}
-        if extra_params:
-            params.update(extra_params)
-
-        if 'oauth_verifier' in self.request.GET:
-            params['oauth_verifier'] = self.request.GET['oauth_verifier']
-        request = OAuthRequest.from_consumer_and_token(self.consumer,
-                                                       token=token,
-                                                       http_url=url,
-                                                       parameters=params)
-        request.sign_request(OAuthSignatureMethod_HMAC_SHA1(), self.consumer,
-                             token)
-        return request
-
-    def fetch_response(self, request):
-        """Executes request and fetchs service response"""
-        self.connection.request(request.http_method, request.to_url())
-        response = self.connection.getresponse()
-        return response.read()
-
-    def access_token(self, token):
-        """Return request for access token value"""
-        request = self.oauth_request(token, self.ACCESS_TOKEN_URL)
-        return OAuthToken.from_string(self.fetch_response(request))
-
-    def user_data(self, access_token):
-        """Loads user data from service"""
-        raise NotImplementedError('Implement in subclass')
-
-    @property
-    def connection(self):
-        """Setups connection"""
-        conn = getattr(self, '_connection', None)
-        if conn is None:
-            conn = httplib.HTTPSConnection(self.SERVER_URL)
-            setattr(self, '_connection', conn)
-        return conn
-
-    @property
-    def consumer(self):
-        """Setups consumer"""
-        cons = getattr(self, '_consumer', None)
-        if cons is None:
-            cons = OAuthConsumer(*self.get_key_and_secret())
-            setattr(self, '_consumer', cons)
-        return cons
-
-    def get_key_and_secret(self):
-        """Return tuple with Consumer Key and Consumer Secret for current
-        service provider. Must return (key, secret), order must be respected.
-        """
-        raise NotImplementedError('Implement in subclass')
-
-
-class BaseGoogleOAuth(ConsumerBasedOAuth):
-    """Base class for Google OAuth mechanism"""
-    AUTHORIZATION_URL = GOOGLE_AUTHORIZATION_URL
-    REQUEST_TOKEN_URL = GOOGLE_REQUEST_TOKEN_URL
-    ACCESS_TOKEN_URL = GOOGLE_ACCESS_TOKEN_URL
-    SERVER_URL = GOOGLE_SERVER
-    AUTH_BACKEND = None
-
-    def user_data(self, access_token):
-        """Loads user data from G service"""
-        raise NotImplementedError('Implement in subclass')
-
-    def get_key_and_secret(self):
-        """Return Consumer Key and Consumer Secret pair"""
-        raise NotImplementedError('Implement in subclass')
-
-
-class OrkutAuth(BaseGoogleOAuth):
-    """Orkut OAuth authentication mechanism"""
-    AUTH_BACKEND = OrkutBackend
-
-    def user_data(self, access_token):
-        """Loads user data from Orkut service"""
-        fields = ORKUT_DEFAULT_DATA
-        if hasattr(settings, 'ORKUT_EXTRA_DATA'):
-            fields += ',' + settings.ORKUT_EXTRA_DATA
-        scope = ORKUT_SCOPE + \
-                getattr(settings, 'ORKUT_EXTRA_SCOPE', [])
-        params = {'method': 'people.get',
-                  'id': 'myself',
-                  'userId': '@me',
-                  'groupId': '@self',
-                  'fields': fields,
-                  'scope': ' '.join(scope)}
-        request = self.oauth_request(access_token, ORKUT_REST_ENDPOINT, params)
-        response = urllib.urlopen(request.to_url()).read()
-        try:
-            return simplejson.loads(response)['data']
-        except (simplejson.JSONDecodeError, KeyError):
-            return None
-
-    def get_key_and_secret(self):
-        """Return Orkut Consumer Key and Consumer Secret pair"""
-        return settings.ORKUT_CONSUMER_KEY, settings.ORKUT_CONSUMER_SECRET
-
-
-class GoogleOAuth(BaseGoogleOAuth):
-    """Google OAuth authorization mechanism"""
-    AUTH_BACKEND = GoogleOAuthBackend
-
-    def user_data(self, access_token):
-        """Loads user data data from googleapis service, only email so far
-        as it's described in:
-            http://sites.google.com/site/oauthgoog/Home/emaildisplayscope
-        OAuth parameters needs to be passed in the queryset and
-        Authorization header, this behavior is listed in:
-            http://groups.google.com/group/oauth/browse_thread/thread/d15add9beb418ebc
-        """
-        url = self.oauth_request(access_token, GOOGLEAPIS_EMAIL,
-                                 {'alt': 'json'}).to_url()
-        params = url.split('?', 1)[1]
-        request = urllib2.Request(url)
-        request.headers['Authorization'] = params # setup header
-        response = urllib2.urlopen(request).read()
-        try:
-            return simplejson.loads(response)['data']
-        except (simplejson.JSONDecodeError, KeyError):
-            return None
-
-    def oauth_request(self, token, url, extra_params=None):
-        extra_params = extra_params or {}
-        scope = GOOGLE_OAUTH_SCOPE + \
-                getattr(settings, 'GOOGLE_OAUTH_EXTRA_SCOPE', [])
-        extra_params.update({
-            'scope': ' '.join(scope),
-            'xoauth_displayname': getattr(settings, 'GOOGLE_DISPLAY_NAME',
-                                          'Social Auth')
-        })
-        return super(GoogleOAuth, self).oauth_request(token, url, extra_params)
-
-    def get_key_and_secret(self):
-        """Return Google OAuth Consumer Key and Consumer Secret pair, uses
-        anonymous by default, beware that this marks the application as not
-        registered and a security badge is displayed on authorization page.
-        http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth
-        """
-        return getattr(settings, 'GOOGLE_CONSUMER_KEY', 'anonymous'), \
-               getattr(settings, 'GOOGLE_CONSUMER_SECRET', 'anonymous')
-
-
-class TwitterAuth(ConsumerBasedOAuth):
-    """Twitter OAuth authentication mechanism"""
-    AUTHORIZATION_URL = TWITTER_AUTHORIZATION_URL
-    REQUEST_TOKEN_URL = TWITTER_REQUEST_TOKEN_URL
-    ACCESS_TOKEN_URL = TWITTER_ACCESS_TOKEN_URL
-    SERVER_URL = TWITTER_SERVER
-    AUTH_BACKEND = TwitterBackend
-
-    def user_data(self, access_token):
-        """Return user data provided"""
-        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 get_key_and_secret(self):
-        """Return Twitter Consumer Key and Consumer Secret pair"""
-        return settings.TWITTER_CONSUMER_KEY, settings.TWITTER_CONSUMER_SECRET
-
-
-class FacebookAuth(BaseOAuth):
-    """Facebook OAuth mechanism"""
-
-    def auth_url(self):
-        """Returns redirect url"""
-        args = {'client_id': settings.FACEBOOK_APP_ID,
-                'redirect_uri': self.redirect_uri}
-        if hasattr(settings, 'FACEBOOK_EXTENDED_PERMISSIONS'):
-            args['scope'] = ','.join(settings.FACEBOOK_EXTENDED_PERMISSIONS)
-        return FACEBOOK_AUTHORIZATION_URL + '?' + urllib.urlencode(args)
-
-    def auth_complete(self, *args, **kwargs):
-        """Returns user, might be logged in"""
-        if 'code' in self.request.GET:
-            url = FACEBOOK_ACCESS_TOKEN_URL + '?' + \
-                  urllib.urlencode({'client_id': settings.FACEBOOK_APP_ID,
-                                'redirect_uri': self.redirect_uri,
-                                'client_secret': settings.FACEBOOK_API_SECRET,
-                                'code': self.request.GET['code']})
-            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
-
-            kwargs.update({'response': data, FacebookBackend.name: True})
-            return authenticate(*args, **kwargs)
-        else:
-            raise ValueError('Authentication error')
-
-    def user_data(self, access_token):
-        """Loads user data from service"""
-        params = {'access_token': access_token}
-        url = FACEBOOK_CHECK_AUTH + '?' + urllib.urlencode(params)
-        try:
-            return simplejson.load(urllib.urlopen(url))
-        except simplejson.JSONDecodeError:
-            return None
-
-
-# Authentication backends
-BACKENDS = {
-    'twitter': TwitterAuth,
-    'facebook': FacebookAuth,
-    'google': GoogleAuth,
-    'google-oauth': GoogleOAuth,
-    'yahoo': YahooAuth,
-    'livejournal': LiveJournalAuth,
-    'orkut': OrkutAuth,
-    'openid': OpenIdAuth,
-}
-
-def get_backend(name, *args, **kwargs):
-    """Return auth backend instance *if* it's registered, None in other case"""
-    return BACKENDS.get(name, lambda *args, **kwargs: None)(*args, **kwargs)
diff --git a/social_auth/backends.py b/social_auth/backends.py
deleted file mode 100644 (file)
index e98505d..0000000
+++ /dev/null
@@ -1,289 +0,0 @@
-"""
-Authentication backeds for django.contrib.auth AUTHENTICATION_BACKENDS setting
-"""
-import urlparse
-from os import urandom
-
-from openid.extensions import ax, sreg
-
-from django.conf import settings
-from django.contrib.auth.backends import ModelBackend
-from django.utils.hashcompat import md5_constructor
-
-from .models import UserSocialAuth
-from .conf import OLD_AX_ATTRS, AX_SCHEMA_ATTRS
-from .signals import pre_update
-
-USERNAME = 'username'
-
-# get User class, could not be auth.User
-User = UserSocialAuth._meta.get_field('user').rel.to
-
-
-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, *args, **kwargs):
-        """Authenticate user using social credentials
-
-        Authentication is made if this is the correct backend, backend
-        verification is made by kwargs inspection for current backend
-        name presence.
-        """
-        # Validate backend and arguments. 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.
-        if not (self.name and kwargs.get(self.name) and 'response' in kwargs):
-            return None
-
-        response = kwargs.get('response')
-        details = self.get_user_details(response)
-        uid = self.get_user_id(details, response)
-        try:
-            social_user = UserSocialAuth.objects.select_related('user')\
-                                                .get(provider=self.name,
-                                                     uid=uid)
-        except UserSocialAuth.DoesNotExist:
-            user = kwargs.get('user')
-            if user is None:  # new user
-                if not getattr(settings, 'SOCIAL_AUTH_CREATE_USERS', True):
-                    return None
-                username = self.username(details)
-                email = details.get('email')
-                user = User.objects.create_user(username=username, email=email)
-            social_user = self.associate_auth(user, uid, response, details)
-        else:
-            user = social_user.user
-
-        # Update user account data.
-        self.update_user_details(user, response, details)
-
-        # Update extra_data storage, unless disabled by setting
-        if getattr(settings, 'SOCIAL_AUTH_EXTRA_DATA', True):
-            extra_data = self.extra_data(user, uid, response, details)
-            if extra_data:
-                social_user.extra_data = extra_data
-                social_user.save()
-
-        return user
-
-    def username(self, details):
-        """Return an unique username, if SOCIAL_AUTH_FORCE_RANDOM_USERNAME
-        setting is True, then username will be a random 30 chars md5 hash
-        """
-        def get_random_username():
-            """Return hash from random string cut at 30 chars"""
-            return md5_constructor(urandom(10)).hexdigest()[:30]
-
-        if getattr(settings, 'SOCIAL_AUTH_FORCE_RANDOM_USERNAME', False):
-            username = get_random_username()
-        elif USERNAME in details:
-            username = details[USERNAME]
-        elif hasattr(settings, 'SOCIAL_AUTH_DEFAULT_USERNAME'):
-            username = settings.SOCIAL_AUTH_DEFAULT_USERNAME
-            if callable(username):
-                username = username()
-        else:
-            username = 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 associate_auth(self, user, uid, response, details):
-        """Associate a Social Auth with an user account."""
-        return UserSocialAuth.objects.create(user=user, uid=uid,
-                                             provider=self.name)
-
-    def extra_data(self, user, uid, response, details):
-        """Return default blank user extra data"""
-        return ''
-
-    def update_user_details(self, user, response, details):
-        """Update user details with (maybe) new data. Username is not
-        changed if associating a new credential."""
-        changed = False  # flag to track changes
-
-        # check if values update should be left to signals handlers only
-        if not getattr(settings, 'SOCIAL_AUTH_CHANGE_SIGNAL_ONLY', False):
-            for name, value in details.iteritems():
-                # do not update username, it was already generated by
-                # self.username(...) and loaded in given instance
-                if name != USERNAME and value and value != getattr(user, name,
-                                                                   value):
-                    setattr(user, name, value)
-                    changed = True
-
-        # Fire a pre-update signal sending current backend instance,
-        # user instance (created or retrieved from database), service
-        # response and processed details, signal handlers must return
-        # True or False to signal that something has changed. Send method
-        # returns a list of tuples with receiver and it's response
-        updated = filter(lambda (receiver, response): response,
-                         pre_update.send(sender=self.__class__, user=user,
-                                         response=response, details=details))
-        if changed or updated:
-            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:
-            {USERNAME: <username if any>,
-             'email': <user email if any>,
-             'fullname': <user full name if any>,
-             'first_name': <user first name if any>,
-             'last_name': <user last name if any>}
-        """
-        raise NotImplementedError('Implement in subclass')
-
-    def get_user(self, user_id):
-        """Return user instance for @user_id"""
-        try:
-            return User.objects.get(pk=user_id)
-        except User.DoesNotExist:
-            return None
-
-
-class OAuthBackend(SocialAuthBackend):
-    """OAuth authentication backend base class"""
-    def get_user_id(self, details, response):
-        "OAuth providers return an unique user id in response"""
-        return response['id']
-
-    def extra_data(self, user, uid, response, details):
-        """Return access_token to store in extra_data field"""
-        return response.get('access_token', '')
-
-
-class TwitterBackend(OAuthBackend):
-    """Twitter OAuth authentication backend"""
-    name = 'twitter'
-
-    def get_user_details(self, response):
-        """Return user details from Twitter account"""
-        return {USERNAME: response['screen_name'],
-                'email': '',  # not supplied
-                'fullname': response['name'],
-                'first_name': response['name'],
-                'last_name': ''}
-
-
-class OrkutBackend(OAuthBackend):
-    """Orkut OAuth authentication backend"""
-    name = 'orkut'
-
-    def get_user_details(self, response):
-        """Return user details from Orkut account"""
-        return {USERNAME: response['displayName'],
-                'email': response['emails'][0]['value'],
-                'fullname': response['displayName'],
-                'firstname': response['name']['givenName'],
-                'lastname': response['name']['familyName']}
-
-
-class GoogleOAuthBackend(OAuthBackend):
-    """Google OAuth authentication backend"""
-    name = 'google-oauth'
-
-    def get_user_id(self, details, response):
-        "Use google email as unique id"""
-        return details['email']
-
-    def get_user_details(self, response):
-        """Return user details from Orkut account"""
-        email = response['email']
-        return {USERNAME: email.split('@', 1)[0],
-                'email': email,
-                'fullname': '',
-                'first_name': '',
-                'last_name': ''}
-
-
-class FacebookBackend(OAuthBackend):
-    """Facebook OAuth authentication backend"""
-    name = 'facebook'
-
-    def get_user_details(self, response):
-        """Return user details from Facebook account"""
-        return {USERNAME: response['name'],
-                'email': response.get('email', ''),
-                'fullname': response['name'],
-                'first_name': response.get('first_name', ''),
-                'last_name': response.get('last_name', '')}
-
-class OpenIDBackend(SocialAuthBackend):
-    """Generic OpenID authentication backend"""
-    name = 'openid'
-
-    def get_user_id(self, details, response):
-        """Return user unique id provided by service"""
-        return response.identity_url
-
-    def get_user_details(self, response):
-        """Return user details from an OpenID request"""
-        values = {USERNAME: '', 'email': '', 'fullname': '',
-                  'first_name': '', 'last_name': ''}
-
-        resp = sreg.SRegResponse.fromSuccessResponse(response)
-        if resp:
-            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)
-        if resp:
-            values.update((alias.replace('old_', ''), resp.getSingle(src))
-                            for src, alias in OLD_AX_ATTRS + AX_SCHEMA_ATTRS)
-
-        fullname = values.get('fullname') or ''
-        first_name = values.get('first_name') or ''
-        last_name = values.get('last_name') or ''
-
-        if not fullname and first_name and last_name:
-            fullname = first_name + ' ' + last_name
-        elif fullname:
-            try:  # Try to split name for django user storage
-                first_name, last_name = fullname.rsplit(' ', 1)
-            except ValueError:
-                last_name = fullname
-
-        values.update({'fullname': fullname, 'first_name': first_name,
-                       'last_name': last_name,
-                       USERNAME: values.get(USERNAME) or \
-                                   (first_name.title() + last_name.title())})
-        return values
-
-
-class GoogleBackend(OpenIDBackend):
-    """Google OpenID authentication backend"""
-    name = 'google'
-
-
-class YahooBackend(OpenIDBackend):
-    """Yahoo OpenID authentication backend"""
-    name = 'yahoo'
-
-
-class LiveJournalBackend(OpenIDBackend):
-    """LiveJournal OpenID authentication backend"""
-    name = 'livejournal'
-
-    def get_user_details(self, response):
-        """Generate username from identity url"""
-        values = super(LiveJournalBackend, self).get_user_details(response)
-        if not values.get(USERNAME):
-            values[USERNAME] = urlparse.urlsplit(response.identity_url)\
-                                       .netloc.split('.', 1)[0]
-        return values
diff --git a/social_auth/backends/__init__.py b/social_auth/backends/__init__.py
new file mode 100644 (file)
index 0000000..813e79d
--- /dev/null
@@ -0,0 +1,515 @@
+"""
+Base backends structures.
+
+This module defines base classes needed to define custom OpenID or OAuth
+auth services from third parties. This customs must subclass an Auth and
+and Backend class, check current implementation for examples.
+
+Also the modules *must* define a BACKENDS dictionary with the backend name
+(which is used for URLs matching) and Auth class, otherwise it won't be
+enabled.
+"""
+from os import urandom, walk
+from os.path import basename
+from httplib import HTTPSConnection
+
+from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions import sreg, ax
+
+from oauth.oauth import OAuthConsumer, OAuthToken, OAuthRequest, \
+                        OAuthSignatureMethod_HMAC_SHA1
+
+from django.conf import settings
+from django.contrib.auth import authenticate
+from django.contrib.auth.backends import ModelBackend
+from django.utils.hashcompat import md5_constructor
+from django.utils.importlib import import_module
+
+from social_auth.models import UserSocialAuth
+from social_auth.store import DjangoOpenIDStore
+from social_auth.signals import pre_update
+
+
+# key for username in user details dict used around, see get_user_details
+# method
+USERNAME = 'username'
+
+# 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', 'first_name'),
+    ('http://axschema.org/namePerson/last', 'last_name'),
+    ('http://axschema.org/namePerson/friendly', 'nickname'),
+]
+SREG_ATTR = ['email', 'fullname', 'nickname']
+OPENID_ID_FIELD = 'openid_identifier'
+SESSION_NAME = 'openid'
+
+# get User class, could not be auth.User
+User = UserSocialAuth._meta.get_field('user').rel.to
+
+
+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, *args, **kwargs):
+        """Authenticate user using social credentials
+
+        Authentication is made if this is the correct backend, backend
+        verification is made by kwargs inspection for current backend
+        name presence.
+        """
+        # Validate backend and arguments. 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.
+        if not (self.name and kwargs.get(self.name) and 'response' in kwargs):
+            return None
+
+        response = kwargs.get('response')
+        details = self.get_user_details(response)
+        uid = self.get_user_id(details, response)
+        try:
+            social_user = UserSocialAuth.objects.select_related('user')\
+                                                .get(provider=self.name,
+                                                     uid=uid)
+        except UserSocialAuth.DoesNotExist:
+            user = kwargs.get('user')
+            if user is None:  # new user
+                if not getattr(settings, 'SOCIAL_AUTH_CREATE_USERS', True):
+                    return None
+                username = self.username(details)
+                email = details.get('email')
+                user = User.objects.create_user(username=username, email=email)
+            social_user = self.associate_auth(user, uid, response, details)
+        else:
+            user = social_user.user
+
+        # Update user account data.
+        self.update_user_details(user, response, details)
+
+        # Update extra_data storage, unless disabled by setting
+        if getattr(settings, 'SOCIAL_AUTH_EXTRA_DATA', True):
+            extra_data = self.extra_data(user, uid, response, details)
+            if extra_data:
+                social_user.extra_data = extra_data
+                social_user.save()
+
+        return user
+
+    def username(self, details):
+        """Return an unique username, if SOCIAL_AUTH_FORCE_RANDOM_USERNAME
+        setting is True, then username will be a random 30 chars md5 hash
+        """
+        def get_random_username():
+            """Return hash from random string cut at 30 chars"""
+            return md5_constructor(urandom(10)).hexdigest()[:30]
+
+        if getattr(settings, 'SOCIAL_AUTH_FORCE_RANDOM_USERNAME', False):
+            username = get_random_username()
+        elif USERNAME in details:
+            username = details[USERNAME]
+        elif hasattr(settings, 'SOCIAL_AUTH_DEFAULT_USERNAME'):
+            username = settings.SOCIAL_AUTH_DEFAULT_USERNAME
+            if callable(username):
+                username = username()
+        else:
+            username = 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 associate_auth(self, user, uid, response, details):
+        """Associate a Social Auth with an user account."""
+        return UserSocialAuth.objects.create(user=user, uid=uid,
+                                             provider=self.name)
+
+    def extra_data(self, user, uid, response, details):
+        """Return default blank user extra data"""
+        return ''
+
+    def update_user_details(self, user, response, details):
+        """Update user details with (maybe) new data. Username is not
+        changed if associating a new credential."""
+        changed = False  # flag to track changes
+
+        # check if values update should be left to signals handlers only
+        if not getattr(settings, 'SOCIAL_AUTH_CHANGE_SIGNAL_ONLY', False):
+            for name, value in details.iteritems():
+                # do not update username, it was already generated by
+                # self.username(...) and loaded in given instance
+                if name != USERNAME and value and value != getattr(user, name,
+                                                                   value):
+                    setattr(user, name, value)
+                    changed = True
+
+        # Fire a pre-update signal sending current backend instance,
+        # user instance (created or retrieved from database), service
+        # response and processed details, signal handlers must return
+        # True or False to signal that something has changed. Send method
+        # returns a list of tuples with receiver and it's response
+        updated = filter(lambda (receiver, response): response,
+                         pre_update.send(sender=self.__class__, user=user,
+                                         response=response, details=details))
+        if changed or updated:
+            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:
+            {USERNAME: <username if any>,
+             'email': <user email if any>,
+             'fullname': <user full name if any>,
+             'first_name': <user first name if any>,
+             'last_name': <user last name if any>}
+        """
+        raise NotImplementedError('Implement in subclass')
+
+    def get_user(self, user_id):
+        """Return user instance for @user_id"""
+        try:
+            return User.objects.get(pk=user_id)
+        except User.DoesNotExist:
+            return None
+
+
+class OAuthBackend(SocialAuthBackend):
+    """OAuth authentication backend base class"""
+    def get_user_id(self, details, response):
+        "OAuth providers return an unique user id in response"""
+        return response['id']
+
+    def extra_data(self, user, uid, response, details):
+        """Return access_token to store in extra_data field"""
+        return response.get('access_token', '')
+
+
+class OpenIDBackend(SocialAuthBackend):
+    """Generic OpenID authentication backend"""
+    name = 'openid'
+
+    def get_user_id(self, details, response):
+        """Return user unique id provided by service"""
+        return response.identity_url
+
+    def get_user_details(self, response):
+        """Return user details from an OpenID request"""
+        values = {USERNAME: '', 'email': '', 'fullname': '',
+                  'first_name': '', 'last_name': ''}
+
+        resp = sreg.SRegResponse.fromSuccessResponse(response)
+        if resp:
+            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)
+        if resp:
+            values.update((alias.replace('old_', ''), resp.getSingle(src))
+                            for src, alias in OLD_AX_ATTRS + AX_SCHEMA_ATTRS)
+
+        fullname = values.get('fullname') or ''
+        first_name = values.get('first_name') or ''
+        last_name = values.get('last_name') or ''
+
+        if not fullname and first_name and last_name:
+            fullname = first_name + ' ' + last_name
+        elif fullname:
+            try:  # Try to split name for django user storage
+                first_name, last_name = fullname.rsplit(' ', 1)
+            except ValueError:
+                last_name = fullname
+
+        values.update({'fullname': fullname, 'first_name': first_name,
+                       'last_name': last_name,
+                       USERNAME: values.get(USERNAME) or \
+                                   (first_name.title() + last_name.title())})
+        return values
+
+
+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, *args, **kwargs):
+        """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 OpenIdAuth(BaseAuth):
+    """
+    OpenId process handling
+        @AUTH_BACKEND   Authorization backend related with this service
+    """
+    AUTH_BACKEND = OpenIDBackend
+
+    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, *args, **kwargs):
+        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:
+            kwargs.update({'response': response, self.AUTH_BACKEND.name: True})
+            return authenticate(*args, **kwargs)
+        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):
+        """Setup request"""
+        openid_request = self.openid_request()
+        # Request some user details. Use attribute exchange if provider
+        # advertises support.
+        if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri):
+            fetch_request = ax.FetchRequest()
+            # Mark all attributes as required, Google ignores optional ones
+            for attr, alias in (AX_SCHEMA_ATTRS + OLD_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):
+        """Return true if openid request will be handled with redirect or
+        HTML content will be returned.
+        """
+        if not hasattr(self, '_uses_redirect'):
+            setattr(self, '_uses_redirect',
+                    self.openid_request().shouldSendRedirect())
+        return getattr(self, '_uses_redirect', True)
+
+    def openid_request(self):
+        """Return openid request"""
+        if not hasattr(self, '_openid_request'):
+            openid_url = self.openid_url()
+            try:
+                openid_request = self.consumer().begin(openid_url)
+            except DiscoveryFailure, err:
+                raise ValueError('OpenID discovery error: %s' % err)
+            else:
+                setattr(self, '_openid_request', openid_request)
+        return getattr(self, '_openid_request', None)
+
+    def openid_url(self):
+        """Return service provider URL.
+        This base class is generic accepting a POST parameter that specifies
+        provider URL."""
+        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 BaseOAuth(BaseAuth):
+    """OAuth base class"""
+    def __init__(self, request, redirect):
+        """Init method"""
+        super(BaseOAuth, self).__init__(request, redirect)
+        self.redirect_uri = self.request.build_absolute_uri(self.redirect)
+
+
+class ConsumerBasedOAuth(BaseOAuth):
+    """Consumer based mechanism OAuth authentication, fill the needed
+    parameters to communicate properly with authentication service.
+
+        @AUTHORIZATION_URL       Authorization service url
+        @REQUEST_TOKEN_URL       Request token URL
+        @ACCESS_TOKEN_URL        Access token URL
+        @SERVER_URL              Authorization server URL
+        @AUTH_BACKEND            Authorization backend related with
+                                 this service
+    """
+    AUTHORIZATION_URL = ''
+    REQUEST_TOKEN_URL = ''
+    ACCESS_TOKEN_URL = ''
+    SERVER_URL = ''
+    AUTH_BACKEND = None
+
+    def auth_url(self):
+        """Returns redirect url"""
+        token = self.unauthorized_token()
+        name = self.AUTH_BACKEND.name + 'unauthorized_token_name'
+        self.request.session[name] = token.to_string()
+        return self.oauth_request(token, self.AUTHORIZATION_URL).to_url()
+
+    def auth_complete(self, *args, **kwargs):
+        """Returns user, might be logged in"""
+        name = self.AUTH_BACKEND.name + 'unauthorized_token_name'
+        unauthed_token = self.request.session.get(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()
+
+        kwargs.update({'response': data, self.AUTH_BACKEND.name: True})
+        return authenticate(*args, **kwargs)
+
+    def unauthorized_token(self):
+        """Return request for unauthorized token (first stage)"""
+        request = self.oauth_request(token=None, url=self.REQUEST_TOKEN_URL)
+        response = self.fetch_response(request)
+        return OAuthToken.from_string(response)
+
+    def oauth_request(self, token, url, extra_params=None):
+        """Generate OAuth request, setups callback url"""
+        params = {'oauth_callback': self.redirect_uri}
+        if extra_params:
+            params.update(extra_params)
+
+        if 'oauth_verifier' in self.request.GET:
+            params['oauth_verifier'] = self.request.GET['oauth_verifier']
+        request = OAuthRequest.from_consumer_and_token(self.consumer,
+                                                       token=token,
+                                                       http_url=url,
+                                                       parameters=params)
+        request.sign_request(OAuthSignatureMethod_HMAC_SHA1(), self.consumer,
+                             token)
+        return request
+
+    def fetch_response(self, request):
+        """Executes request and fetchs service response"""
+        self.connection.request(request.http_method, request.to_url())
+        response = self.connection.getresponse()
+        return response.read()
+
+    def access_token(self, token):
+        """Return request for access token value"""
+        request = self.oauth_request(token, self.ACCESS_TOKEN_URL)
+        return OAuthToken.from_string(self.fetch_response(request))
+
+    def user_data(self, access_token):
+        """Loads user data from service"""
+        raise NotImplementedError('Implement in subclass')
+
+    @property
+    def connection(self):
+        """Setups connection"""
+        conn = getattr(self, '_connection', None)
+        if conn is None:
+            conn = HTTPSConnection(self.SERVER_URL)
+            setattr(self, '_connection', conn)
+        return conn
+
+    @property
+    def consumer(self):
+        """Setups consumer"""
+        cons = getattr(self, '_consumer', None)
+        if cons is None:
+            cons = OAuthConsumer(*self.get_key_and_secret())
+            setattr(self, '_consumer', cons)
+        return cons
+
+    def get_key_and_secret(self):
+        """Return tuple with Consumer Key and Consumer Secret for current
+        service provider. Must return (key, secret), order must be respected.
+        """
+        raise NotImplementedError('Implement in subclass')
+
+
+# import sources from where check for auth backends
+SOCIAL_AUTH_IMPORT_SOURCES = (
+    'social_auth.backends',
+    'social_auth.backends.contrib',
+) + getattr(settings, 'SOCIAL_AUTH_IMPORT_BACKENDS', ())
+
+def get_backends():
+    backends = {}
+
+    for mod_name in SOCIAL_AUTH_IMPORT_SOURCES:
+        try:
+            mod = import_module(mod_name)
+        except ImportError:
+            continue
+
+        for directory, subdir, files in walk(mod.__path__[0]):
+            for name in filter(lambda name: name.endswith('.py'), files):
+                try:
+                    name = basename(name).replace('.py', '')
+                    sub = import_module(mod_name + '.' + name)
+                    backends.update(sub.BACKENDS)
+                except (ImportError, AttributeError):
+                    pass
+    return backends
+
+# load backends from defined modules
+BACKENDS = get_backends()
+
+def get_backend(name, *args, **kwargs):
+    """Return auth backend instance *if* it's registered, None in other case"""
+    return BACKENDS.get(name, lambda *args, **kwargs: None)(*args, **kwargs)
diff --git a/social_auth/backends/contrib/__init__.py b/social_auth/backends/contrib/__init__.py
new file mode 100644 (file)
index 0000000..914e3c1
--- /dev/null
@@ -0,0 +1 @@
+"""Contrib auth modules"""
diff --git a/social_auth/backends/contrib/livejournal.py b/social_auth/backends/contrib/livejournal.py
new file mode 100644 (file)
index 0000000..1b1b18d
--- /dev/null
@@ -0,0 +1,49 @@
+"""
+LiveJournal OpenID support.
+
+This contribution adds support for LiveJournal OpenID service in the form
+username.livejournal.com. Username is retrieved from the identity url.
+"""
+import urlparse
+
+from social_auth.backends import OpenIDBackend, OpenIdAuth, USERNAME
+
+
+# LiveJournal conf
+LIVEJOURNAL_URL = 'http://%s.livejournal.com'
+LIVEJOURNAL_USER_FIELD = 'openid_lj_user'
+
+
+class LiveJournalBackend(OpenIDBackend):
+    """LiveJournal OpenID authentication backend"""
+    name = 'livejournal'
+
+    def get_user_details(self, response):
+        """Generate username from identity url"""
+        values = super(LiveJournalBackend, self).get_user_details(response)
+        values[USERNAME] = values.get(USERNAME) or \
+                           urlparse.urlsplit(response.identity_url)\
+                                   .netloc.split('.', 1)[0]
+        return values
+
+
+class LiveJournalAuth(OpenIdAuth):
+    """LiveJournal OpenID authentication"""
+    AUTH_BACKEND = LiveJournalBackend
+
+    def uses_redirect(self):
+        """LiveJournal uses redirect"""
+        return True
+
+    def openid_url(self):
+        """Returns LiveJournal authentication URL"""
+        if self.request.method != 'POST' or \
+           not self.request.POST.get(LIVEJOURNAL_USER_FIELD):
+            raise ValueError, 'Missing LiveJournal user identifier'
+        return LIVEJOURNAL_URL % self.request.POST[LIVEJOURNAL_USER_FIELD]
+
+
+# Backend definition
+BACKENDS = {
+    'livejournal': LiveJournalAuth,
+}
diff --git a/social_auth/backends/contrib/orkut.py b/social_auth/backends/contrib/orkut.py
new file mode 100644 (file)
index 0000000..9356063
--- /dev/null
@@ -0,0 +1,74 @@
+"""
+Orkut OAuth support.
+
+This contribution adds support for Orkut OAuth service. The scope is
+limited to http://orkut.gmodules.com/social/ by default, but can be
+extended with ORKUT_EXTRA_SCOPE on project settings. Also name, display
+name and emails are the default requested user data, but extra values
+can be specified by defining ORKUT_EXTRA_DATA setting.
+
+OAuth settings ORKUT_CONSUMER_KEY and ORKUT_CONSUMER_SECRET are needed
+to enable this service support.
+"""
+import urllib
+
+from django.conf import settings
+from django.utils import simplejson
+
+from social_auth.backends import OAuthBackend, USERNAME
+from social_auth.backends.google import BaseGoogleOAuth
+
+
+# Orkut configuration
+# default scope, specify extra scope in settings as in:
+# ORKUT_EXTRA_SCOPE = ['...']
+ORKUT_SCOPE = ['http://orkut.gmodules.com/social/']
+ORKUT_REST_ENDPOINT = 'http://www.orkut.com/social/rpc'
+ORKUT_DEFAULT_DATA = 'name,displayName,emails'
+
+
+class OrkutBackend(OAuthBackend):
+    """Orkut OAuth authentication backend"""
+    name = 'orkut'
+
+    def get_user_details(self, response):
+        """Return user details from Orkut account"""
+        return {USERNAME: response['displayName'],
+                'email': response['emails'][0]['value'],
+                'fullname': response['displayName'],
+                'firstname': response['name']['givenName'],
+                'lastname': response['name']['familyName']}
+
+
+class OrkutAuth(BaseGoogleOAuth):
+    """Orkut OAuth authentication mechanism"""
+    AUTH_BACKEND = OrkutBackend
+
+    def user_data(self, access_token):
+        """Loads user data from Orkut service"""
+        fields = ORKUT_DEFAULT_DATA
+        if hasattr(settings, 'ORKUT_EXTRA_DATA'):
+            fields += ',' + settings.ORKUT_EXTRA_DATA
+        scope = ORKUT_SCOPE + getattr(settings, 'ORKUT_EXTRA_SCOPE', [])
+        params = {'method': 'people.get',
+                  'id': 'myself',
+                  'userId': '@me',
+                  'groupId': '@self',
+                  'fields': fields,
+                  'scope': ' '.join(scope)}
+        request = self.oauth_request(access_token, ORKUT_REST_ENDPOINT, params)
+        response = urllib.urlopen(request.to_url()).read()
+        try:
+            return simplejson.loads(response)['data']
+        except (simplejson.JSONDecodeError, KeyError):
+            return None
+
+    def get_key_and_secret(self):
+        """Return Orkut Consumer Key and Consumer Secret pair"""
+        return settings.ORKUT_CONSUMER_KEY, settings.ORKUT_CONSUMER_SECRET
+
+
+# Backend definition
+BACKENDS = {
+    'orkut': OrkutAuth,
+}
diff --git a/social_auth/backends/facebook.py b/social_auth/backends/facebook.py
new file mode 100644 (file)
index 0000000..eff17d6
--- /dev/null
@@ -0,0 +1,86 @@
+"""
+Facebook OAuth support.
+
+This contribution adds support for Facebook OAuth service. The settings
+FACEBOOK_APP_ID and FACEBOOK_API_SECRET must be defined with the values
+given by Facebook application registration process.
+
+Extended permissions are supported by defining FACEBOOK_EXTENDED_PERMISSIONS
+setting, it must be a list of values to request.
+"""
+import cgi
+import urllib
+
+from django.conf import settings
+from django.utils import simplejson
+from django.contrib.auth import authenticate
+
+from social_auth.backends import BaseOAuth, OAuthBackend, USERNAME
+
+
+# Facebook configuration
+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
+
+
+class FacebookBackend(OAuthBackend):
+    """Facebook OAuth authentication backend"""
+    name = 'facebook'
+
+    def get_user_details(self, response):
+        """Return user details from Facebook account"""
+        return {USERNAME: response['name'],
+                'email': response.get('email', ''),
+                'fullname': response['name'],
+                'first_name': response.get('first_name', ''),
+                'last_name': response.get('last_name', '')}
+
+
+class FacebookAuth(BaseOAuth):
+    """Facebook OAuth mechanism"""
+    def auth_url(self):
+        """Returns redirect url"""
+        args = {'client_id': settings.FACEBOOK_APP_ID,
+                'redirect_uri': self.redirect_uri}
+        if hasattr(settings, 'FACEBOOK_EXTENDED_PERMISSIONS'):
+            args['scope'] = ','.join(settings.FACEBOOK_EXTENDED_PERMISSIONS)
+        return FACEBOOK_AUTHORIZATION_URL + '?' + urllib.urlencode(args)
+
+    def auth_complete(self, *args, **kwargs):
+        """Returns user, might be logged in"""
+        if 'code' in self.request.GET:
+            url = FACEBOOK_ACCESS_TOKEN_URL + '?' + \
+                  urllib.urlencode({'client_id': settings.FACEBOOK_APP_ID,
+                                'redirect_uri': self.redirect_uri,
+                                'client_secret': settings.FACEBOOK_API_SECRET,
+                                'code': self.request.GET['code']})
+            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
+
+            kwargs.update({'response': data, FacebookBackend.name: True})
+            return authenticate(*args, **kwargs)
+        else:
+            raise ValueError('Authentication error')
+
+    def user_data(self, access_token):
+        """Loads user data from service"""
+        params = {'access_token': access_token}
+        url = FACEBOOK_CHECK_AUTH + '?' + urllib.urlencode(params)
+        try:
+            return simplejson.load(urllib.urlopen(url))
+        except simplejson.JSONDecodeError:
+            return None
+
+
+# Backend definition
+BACKENDS = {
+    'facebook': FacebookAuth,
+}
diff --git a/social_auth/backends/google.py b/social_auth/backends/google.py
new file mode 100644 (file)
index 0000000..c2a1249
--- /dev/null
@@ -0,0 +1,133 @@
+"""
+Google OpenID and OAuth support
+
+OAuth works straightforward using anonymous configurations, username
+is generated by requesting email to the not documented, googleapis.com
+service. Registered applications can define settings GOOGLE_CONSUMER_KEY
+and GOOGLE_CONSUMER_SECRET and they will be used in the auth process.
+Setting GOOGLE_OAUTH_EXTRA_SCOPE can be used to access different user
+related data, like calendar, contacts, docs, etc.
+
+OpenID also works straightforward, it doesn't need further configurations.
+"""
+from urllib2 import Request, urlopen
+
+from django.conf import settings
+from django.utils import simplejson
+
+from social_auth.backends import OpenIdAuth, ConsumerBasedOAuth, \
+                                 OAuthBackend, OpenIDBackend, USERNAME
+
+
+# Google OAuth base configuration
+GOOGLE_SERVER = 'www.google.com'
+GOOGLE_REQUEST_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetRequestToken'
+GOOGLE_ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken'
+GOOGLE_AUTHORIZATION_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken'
+# scope for user email, specify extra scopes in settings, for example:
+# GOOGLE_OAUTH_EXTRA_SCOPE = ['https://www.google.com/m8/feeds/']
+GOOGLE_OAUTH_SCOPE = ['https://www.googleapis.com/auth/userinfo#email']
+GOOGLEAPIS_EMAIL = 'https://www.googleapis.com/userinfo/email'
+GOOGLE_OPENID_URL = 'https://www.google.com/accounts/o8/id'
+
+
+# Backends
+class GoogleOAuthBackend(OAuthBackend):
+    """Google OAuth authentication backend"""
+    name = 'google-oauth'
+
+    def get_user_id(self, details, response):
+        "Use google email as unique id"""
+        return details['email']
+
+    def get_user_details(self, response):
+        """Return user details from Orkut account"""
+        email = response['email']
+        return {USERNAME: email.split('@', 1)[0],
+                'email': email,
+                'fullname': '',
+                'first_name': '',
+                'last_name': ''}
+
+
+class GoogleBackend(OpenIDBackend):
+    """Google OpenID authentication backend"""
+    name = 'google'
+
+
+# Auth classes
+class GoogleAuth(OpenIdAuth):
+    """Google OpenID authentication"""
+    AUTH_BACKEND = GoogleBackend
+
+    def openid_url(self):
+        """Return Google OpenID service url"""
+        return GOOGLE_OPENID_URL
+
+
+class BaseGoogleOAuth(ConsumerBasedOAuth):
+    """Base class for Google OAuth mechanism"""
+    AUTHORIZATION_URL = GOOGLE_AUTHORIZATION_URL
+    REQUEST_TOKEN_URL = GOOGLE_REQUEST_TOKEN_URL
+    ACCESS_TOKEN_URL = GOOGLE_ACCESS_TOKEN_URL
+    SERVER_URL = GOOGLE_SERVER
+    AUTH_BACKEND = None
+
+    def user_data(self, access_token):
+        """Loads user data from G service"""
+        raise NotImplementedError('Implement in subclass')
+
+    def get_key_and_secret(self):
+        """Return Consumer Key and Consumer Secret pair"""
+        raise NotImplementedError('Implement in subclass')
+
+
+class GoogleOAuth(BaseGoogleOAuth):
+    """Google OAuth authorization mechanism"""
+    AUTH_BACKEND = GoogleOAuthBackend
+
+    def user_data(self, access_token):
+        """Loads user data data from googleapis service, only email so far
+        as it's described in:
+            http://sites.google.com/site/oauthgoog/Home/emaildisplayscope
+        OAuth parameters needs to be passed in the queryset and
+        Authorization header, this behavior is listed in:
+            http://groups.google.com/group/oauth/browse_thread/thread/d15add9beb418ebc
+        """
+        url = self.oauth_request(access_token, GOOGLEAPIS_EMAIL,
+                                 {'alt': 'json'}).to_url()
+        params = url.split('?', 1)[1]
+        request = Request(url)
+        request.headers['Authorization'] = params  # setup header
+        response = urlopen(request).read()
+        try:
+            return simplejson.loads(response)['data']
+        except (simplejson.JSONDecodeError, KeyError):
+            return None
+
+    def oauth_request(self, token, url, extra_params=None):
+        extra_params = extra_params or {}
+        scope = GOOGLE_OAUTH_SCOPE + \
+                getattr(settings, 'GOOGLE_OAUTH_EXTRA_SCOPE', [])
+        extra_params.update({
+            'scope': ' '.join(scope),
+            'xoauth_displayname': getattr(settings, 'GOOGLE_DISPLAY_NAME',
+                                          'Social Auth')
+        })
+        return super(GoogleOAuth, self).oauth_request(token, url, extra_params)
+
+    def get_key_and_secret(self):
+        """Return Google OAuth Consumer Key and Consumer Secret pair, uses
+        anonymous by default, beware that this marks the application as not
+        registered and a security badge is displayed on authorization page.
+        http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth
+        """
+        return getattr(settings, 'GOOGLE_CONSUMER_KEY', 'anonymous'), \
+               getattr(settings, 'GOOGLE_CONSUMER_SECRET', 'anonymous')
+
+
+# Backend definition
+BACKENDS = {
+    'google': GoogleAuth,
+    'google-oauth': GoogleOAuth,
+}
diff --git a/social_auth/backends/twitter.py b/social_auth/backends/twitter.py
new file mode 100644 (file)
index 0000000..04c47e3
--- /dev/null
@@ -0,0 +1,65 @@
+"""
+Twitter OAuth support.
+
+This adds support for Twitter OAuth service. An application must
+be registered first on twitter and the settings TWITTER_CONSUMER_KEY
+and TWITTER_CONSUMER_SECRET must be defined with they corresponding
+values.
+
+User screen name is used to generate username.
+"""
+from django.conf import settings
+from django.utils import simplejson
+
+from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, USERNAME
+
+
+# Twitter configuration
+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
+# Note: oauth/authorize forces the user to authorize every time.
+#       oauth/authenticate uses their previous selection, barring revocation.
+TWITTER_AUTHORIZATION_URL = 'http://%s/oauth/authenticate' % TWITTER_SERVER
+TWITTER_CHECK_AUTH = 'https://twitter.com/account/verify_credentials.json'
+
+
+class TwitterBackend(OAuthBackend):
+    """Twitter OAuth authentication backend"""
+    name = 'twitter'
+
+    def get_user_details(self, response):
+        """Return user details from Twitter account"""
+        return {USERNAME: response['screen_name'],
+                'email': '',  # not supplied
+                'fullname': response['name'],
+                'first_name': response['name'],
+                'last_name': ''}
+
+
+class TwitterAuth(ConsumerBasedOAuth):
+    """Twitter OAuth authentication mechanism"""
+    AUTHORIZATION_URL = TWITTER_AUTHORIZATION_URL
+    REQUEST_TOKEN_URL = TWITTER_REQUEST_TOKEN_URL
+    ACCESS_TOKEN_URL = TWITTER_ACCESS_TOKEN_URL
+    SERVER_URL = TWITTER_SERVER
+    AUTH_BACKEND = TwitterBackend
+
+    def user_data(self, access_token):
+        """Return user data provided"""
+        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 get_key_and_secret(self):
+        """Return Twitter Consumer Key and Consumer Secret pair"""
+        return settings.TWITTER_CONSUMER_KEY, settings.TWITTER_CONSUMER_SECRET
+
+
+# Backend definition
+BACKENDS = {
+    'twitter': TwitterAuth,
+}
diff --git a/social_auth/backends/yahoo.py b/social_auth/backends/yahoo.py
new file mode 100644 (file)
index 0000000..e9362f4
--- /dev/null
@@ -0,0 +1,29 @@
+"""
+Yahoo OpenID support
+
+No extra configurations are needed to make this work.
+"""
+from social_auth.backends import OpenIDBackend, OpenIdAuth
+
+
+YAHOO_OPENID_URL = 'http://yahoo.com'
+
+
+class YahooBackend(OpenIDBackend):
+    """Yahoo OpenID authentication backend"""
+    name = 'yahoo'
+
+
+class YahooAuth(OpenIdAuth):
+    """Yahoo OpenID authentication"""
+    AUTH_BACKEND = YahooBackend
+
+    def openid_url(self):
+        """Return Yahoo OpenID service url"""
+        return YAHOO_OPENID_URL
+
+
+# Backend definition
+BACKENDS = {
+    'yahoo': YahooAuth,
+}
diff --git a/social_auth/conf.py b/social_auth/conf.py
deleted file mode 100644 (file)
index 3fcee41..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-"""Conf settings"""
-# Twitter configuration
-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
-# Note: oauth/authorize forces the user to authorize every time.
-#       oauth/authenticate uses their previous selection, barring revocation.
-TWITTER_AUTHORIZATION_URL = 'http://%s/oauth/authenticate' % TWITTER_SERVER
-TWITTER_CHECK_AUTH = 'https://twitter.com/account/verify_credentials.json'
-
-# Facebook configuration
-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
-
-# Google OAuth base configuration
-GOOGLE_SERVER = 'www.google.com'
-GOOGLE_REQUEST_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetRequestToken'
-GOOGLE_ACCESS_TOKEN_URL  = 'https://www.google.com/accounts/OAuthGetAccessToken'
-GOOGLE_AUTHORIZATION_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken'
-# scope for user email, specify extra scopes in settings, for example:
-# GOOGLE_OAUTH_EXTRA_SCOPE = ['https://www.google.com/m8/feeds/']
-GOOGLE_OAUTH_SCOPE       = ['https://www.googleapis.com/auth/userinfo#email']
-GOOGLEAPIS_EMAIL         = 'https://www.googleapis.com/userinfo/email'
-
-# Orkut configuration
-# default scope, specify extra scope in settings as in:
-# ORKUT_EXTRA_SCOPE = ['...']
-ORKUT_SCOPE = ['http://orkut.gmodules.com/social/']
-ORKUT_REST_ENDPOINT = 'http://www.orkut.com/social/rpc'
-ORKUT_DEFAULT_DATA = 'name,displayName,emails'
-
-# 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', 'first_name'),
-    ('http://axschema.org/namePerson/last', 'last_name'),
-    ('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'
-OPENID_LIVEJOURNAL_URL = 'http://%s.livejournal.com'
-OPENID_LIVEJOURNAL_USER_FIELD = 'openid_lj_user'
index ab7ebb44ddbd7f212a5a7cb60ed024c7c29f30a0..a1be029011e79eebc2c2be46946f3f7e8958edb8 100644 (file)
@@ -6,7 +6,7 @@ from openid.association import Association as OIDAssociation
 from openid.store.interface import OpenIDStore
 from openid.store.nonce import SKEW
 
-from .models import Association, Nonce
+from social_auth.models import Association, Nonce
 
 
 class DjangoOpenIDStore(OpenIDStore):
index 21854998c73d0b1b2cda5f6a6ef53267c7fd8f6f..b9f19f807b4cce25132316faed97adf0738e7357 100644 (file)
@@ -1,7 +1,7 @@
 """URLs module"""
 from django.conf.urls.defaults import patterns, url
 
-from .views import auth, complete, associate, associate_complete
+from social_auth.views import auth, complete, associate, associate_complete
 
 
 urlpatterns = patterns('',
index 248d6fa1d4a3007384fcbe87f1e51b35b1ca9df8..ed35db72e7eac38127e857f587d000c53a26c46e 100644 (file)
@@ -6,7 +6,7 @@ from django.core.urlresolvers import reverse
 from django.contrib.auth import login, REDIRECT_FIELD_NAME
 from django.contrib.auth.decorators import login_required
 
-from .auth import get_backend
+from social_auth.backends import get_backend
 
 
 def auth(request, backend):