Commits

Gregory Petukhov committed ec79c65

Awesome refactoring. Implement oauth service and configure it for twitter authorization

  • Participants
  • Parent commits 3e36d07

Comments (0)

Files changed (18)

 recursive-include xauth *
-recursive-include demo *
 global-exclude *.pyc
 ========================
 
 Django-xauth is django application which is all-in-one solution for registration
-on the site via different 3rd part services like openid, oauth, etc.
+on the site via different 3rd part services like openid, oauth, etc. Xauth consists of
+services. For now only openid and oauth is available. Each service enables your users to
+use a number of popular websites. Openid service allows to use google, yandex, yahoo, etc.
+Oauth allows to use twitter, vkontakte, etc.
 
 
 Dependencies
 ============
 * Django >= 1.3
+* openid service depends on python-openid
+* oauth service depends on python-oauth2
 
 
-Installation
-============
+Installation (read carefully)
+=============================
 
-* Install django-xauth package (for now only via cloning the repo and putting into sys.path)
-* Install dependencies (for the moment of writing this file only openid service is available, hence only python-openid dependency is required)
+* Install django-xauth package using pip or easy_install (anyway for now it is better use recent version
+  from the bitbucket)
+* Install dependencies
 * Put "xauth" into INSTALLED_APPS
 * Put "xauth.backends.XauthBackend" into AUTHENTICATION_BACKENDS
 * Put "from xauth.settings import *" to import default xauth settings

File demo/build/pipreq.txt

 Django
 python-openid
+-e git+https://github.com/simplegeo/python-oauth2#egg=oauth2

File demo/settings.py

 XAUTH_LOGOUT_URL = '/'
 XAUTH_ACCOUNT_FORM = 'account.forms.SignupForm'
 XAUTH_PROFILE_FIELDS = ['nickname', 'email', 'fullname', 'country']
+XAUTH_OAUTH_TWITTER_CONSUMER_KEY = '2W0gPV3RxSaT5U37EIgIaQ'
+XAUTH_OAUTH_TWITTER_CONSUMER_SECRET = 'XnJ4KqceE8emoV4wvKXbYjGJmfe5aoWdUKCqgDIOM'
 
 
     setup(
-        version = '0.0.2',
+        version = '0.0.3',
         description = 'All-in-one django registration application',
         author = 'Grigoriy Petukhov',
         author_email = 'lorien@lorien.name',

File xauth/backends.py

 
 from django.contrib.auth.models import User
 
+from xauth.models import XauthAssociation
+
 logger = logging.getLogger('xauth.backends')
 
 class XauthBackend(object):
     supports_inactive_user = True
 
     def authenticate(self, xauth_response):
-        service = xauth_response['service']
-        logger.debug('Authenticating user with %s xauth service' % service)
+        logger.debug('Authenticating user with %s xauth service' % xauth_response['service'])
+
         try:
-            module = __import__('xauth.service.%s.backend' % service, globals(), locals(), ['foo'])
-            auth_func = getattr(module, 'authenticate')
-        except (ImportError, AttributeError), ex:
-            logger.error('Could not import service authentication backend', exc_info=ex)
+            assoc = XauthAssociation.objects.get(identity=xauth_response['identity'])
+        except XauthAssociation.DoesNotExist:
+            return None
         else:
-            return auth_func(xauth_response)
+            return assoc.user
 
     def get_user(self, user_id):
         try:

File xauth/service/oauth/__init__.py

Empty file added.

File xauth/service/oauth/settings.py

+PROVIDERS = {
+    'twitter': {
+        'request_token_url': 'http://twitter.com/oauth/request_token',
+        'access_token_url': 'http://twitter.com/oauth/access_token',
+        'authenticate_url': 'http://twitter.com/oauth/authenticate',
+    },
+}

File xauth/service/oauth/views.py

+from __future__ import absolute_import
+import oauth2
+import urlparse
+import urllib
+import time
+
+import logging
+
+from django.shortcuts import render, redirect
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.contrib import messages
+from django.utils.translation import ugettext as _
+
+from xauth.util import absolute_url
+from xauth.service.oauth.settings import PROVIDERS
+
+logger = logging.getLogger('xauth.service.xauth.views')
+
+def get_setting(provider, key):
+    if key in ['request_token_url', 'access_token_url', 'authenticate_url']:
+        return PROVIDERS[provider][key]
+    else:
+        return getattr(settings, 'XAUTH_OAUTH_%s_%s' % (provider.upper(), key.upper()))
+
+def prepare(request):
+    """
+    Receive request token.
+    """
+
+    logger.debug('VIEW_START prepare')
+
+    provider = request.GET.get('provider')
+    logger.debug('%s provider choosed' % provider)
+
+    if not provider in PROVIDERS:
+        messages.error(request, u'Invalid request')
+        return redirect('xauth_login')
+
+    consumer = oauth2.Consumer(get_setting(provider, 'consumer_key'),
+                          get_setting(provider, 'consumer_secret'))
+    client = oauth2.Client(consumer)
+
+    sid = str(time.time())
+    return_to = absolute_url(reverse('xauth_exec', args=['oauth', 'finish']) + '?sid=%s' % sid)
+    request_url = get_setting(provider, 'request_token_url') + '?oauth_callback=%s' % urllib.quote(return_to)
+    resp, content = client.request(request_url, 'GET')
+    if resp['status'] != '200':
+        messages.error(request, u'Invalid response from oauth provider [code=%s]' % resp['status'])
+        logging.error('Invalid response from oauth provider with code %s' % resp['status'])
+        logging.error(u'[start]%s[end]' % content)
+        return redirect('xauth_login')
+    else:
+        oauth_state = dict(urlparse.parse_qsl(content))
+        oauth_state['provider'] = provider
+        request.session['xauth_oauth_%s' % sid] = oauth_state
+
+        return redirect('%s?oauth_token=%s' % (
+            get_setting(provider, 'authenticate_url'),
+            oauth_state['oauth_token'])) 
+
+
+def finish(request):
+    """
+    Finish the oauth authentication process.
+    """
+
+    logger.debug('VIEW START finish')
+
+    sid = request.GET.get('sid')
+    oauth_state = request.session.get('xauth_oauth_%s' % sid)
+    if not oauth_state:
+        messages.error(request, u'Invalid authentication data')
+    provider = oauth_state['provider']
+
+    token = oauth2.Token(
+        oauth_state['oauth_token'],
+        oauth_state['oauth_token_secret'])
+    consumer = oauth2.Consumer(get_setting(provider, 'consumer_key'),
+                          get_setting(provider, 'consumer_secret'))
+    client = oauth2.Client(consumer, token)
+
+    resp, content = client.request(get_setting(provider, 'access_token_url'), 'GET')
+    if resp['status'] != '200':
+        messages.error(request, u'Invalid response from oauth provider [code=%s]' % resp['status'])
+        logging.error('Invalid response from oauth provider with code %s' % resp['status'])
+        logging.error(u'[start]%s[end]' % content)
+        return redirect('xauth_login')
+
+    response = dict(urlparse.parse_qsl(content))
+    profile = {'nickname': response['screen_name']}
+    identity = 'http://twitter.com/%s' % response['screen_name']
+
+    request.session['xauth_response'] = {
+        'identity': identity,
+        'service': 'oauth',
+        'profile': profile,
+    }
+
+    return redirect('xauth_complete')

File xauth/service/openid/backend.py

-from xauth.models import XauthAssociation
-
-def authenticate(xauth_response):
-    identity = xauth_response['response'].identity_url
-
-    try:
-        assoc = XauthAssociation.objects.get(identity=identity)
-    except XauthAssociation.DoesNotExist:
-        return None
-    else:
-        return assoc.user

File xauth/service/openid/forms.py

-from django import forms
-
-class PrepareForm(forms.Form):
-    openid_url = forms.CharField()

File xauth/service/openid/util.py

-import logging
-
-from xauth.models import XauthAssociation
-
-logger = logging.getLogger('xauth.service.openid.util')
-
-def save_association(user, xauth_response):
-    assoc = XauthAssociation()
-    assoc.user = user
-    assoc.identity = xauth_response['response'].identity_url
-    assoc.service = xauth_response['service']
-    assoc.save()

File xauth/service/openid/views.py

 from __future__ import absolute_import
+import re
 
 import logging
 from openid.consumer import consumer
 from django.contrib import messages
 from django.utils.translation import ugettext as _
 
-from xauth.service.openid.forms import PrepareForm
 from xauth.util import absolute_url
 from xauth.service.openid.profile import add_profile_query, process_profile_data
 
     """
 
     logger.debug('VIEW_START prepare')
-    form = PrepareForm(request.POST or None)
-    if form.is_valid():
-        openid_url = form.cleaned_data['openid_url']
-        logger.debug('Processing %s OpenID URL' % openid_url)
-        con = get_consumer(request)
 
-        try:
-            auth_request = con.begin(openid_url)
-        except DiscoveryFailure, ex:
-            logger.error('DiscoveryFailure', exc_info=ex)
-            messages.error(request, _('OpenID discovery error: %s' % ex))
+    openid_url = request.GET.get('openid_url')
+    if not openid_url:
+        messages.error(request, u'Invalid openid URL')
+        return redirect('xauth_login')
+
+    logger.debug('Processing %s OpenID URL' % openid_url)
+    con = get_consumer(request)
+
+    try:
+        auth_request = con.begin(openid_url)
+    except DiscoveryFailure, ex:
+        logger.error('DiscoveryFailure', exc_info=ex)
+        messages.error(request, _('OpenID discovery error: %s' % ex))
+        return redirect('xauth_login')
+    else:
+        add_profile_query(auth_request, settings.XAUTH_PROFILE_FIELDS)
+
+        # Compute the trust root and return URL values to build the
+        # redirect information.
+        trust_root = absolute_url('')
+        return_to = absolute_url(reverse('xauth_exec', args=['openid', 'finish']))
+
+        # Send the browser to the server either by sending a redirect
+        # URL or by generating a POST form.
+        if auth_request.shouldSendRedirect():
+            return redirect(auth_request.redirectURL(trust_root, return_to))
         else:
-            add_profile_query(auth_request, settings.XAUTH_PROFILE_FIELDS)
+            # Beware: this renders a template whose content is a form
+            # and some javascript to submit it upon page load.  Non-JS
+            # users will have to click the form submit button to
+            # initiate OpenID authentication.
+            form_html = auth_request.formMarkup(
+                trust_root, return_to, False, {'id': 'openid_message'})
+            context = {'html': form_html}
+            return render(request, 'xauth/openid/request_form.html', context)
 
-            # Compute the trust root and return URL values to build the
-            # redirect information.
-            trust_root = absolute_url('')
-            return_to = absolute_url(reverse('xauth_exec', args=['openid', 'finish']))
-
-            # Send the browser to the server either by sending a redirect
-            # URL or by generating a POST form.
-            if auth_request.shouldSendRedirect():
-                return redirect(auth_request.redirectURL(trust_root, return_to))
-            else:
-                # Beware: this renders a template whose content is a form
-                # and some javascript to submit it upon page load.  Non-JS
-                # users will have to click the form submit button to
-                # initiate OpenID authentication.
-                form_html = auth_request.formMarkup(
-                    trust_root, return_to, False, {'id': 'openid_message'})
-                context = {'html': form_html}
-                return render(request, 'xauth/openid/request_form.html', context)
-
-    context = {
-        'form': form,
-    }
-    return render(request, 'xauth/openid/prepare.html', context)
+    assert False
 
 
 def finish(request):
         profile = process_profile_data(response, settings.XAUTH_PROFILE_FIELDS)
 
         if response.status == consumer.SUCCESS:
+            identity = response.identity_url
             request.session['xauth_response'] = {
                 'service': 'openid',
-                'response': response,
+                #'response': response,
+                'identity': identity,
                 'profile': profile,
                 #{'url': response.getDisplayIdentifier(),
             }
             return redirect('xauth_complete')
         else:
             messages.error(request, _('OpenID authentication failure: %s' % response.message))
-            return redirect('xauth_prepare', 'openid')
-
-    return render(request, 'xauth/openid/prepare.html', **result)
+            return redirect('xauth_login')
+    else:
+        messages.error(request, u'Invalid openid data')
+        return redirect('xauth_login')

File xauth/settings.py

 XAUTH_SERVICES = (
     'openid',
+    'oauth',
 )
 XAUTH_HOSTNAME = 'localhost:8000'
 XAUTH_SIGNUP_SUCCESS_URL = '/'

File xauth/templates/xauth/identity_list.html

 <p>{% trans "No identitites." %}</p>
 {% endif %}
 
+<p>You can associate <a href="{% url xauth_login %}">more identities</a>.</p>
+
 {% endblock %}

File xauth/templates/xauth/login.html

 {% load i18n %}
 
 {% block xauth_content %}
-<h2>{% trans "Choose authentication service" %}</h2>
+<h2>{% trans "Log in to the site using your favourite service" %}</h2>
 <ul>
-    <li><a href="{% url xauth_prepare "openid" %}">OpenID</a></li>
+    <li><a href="{% url xauth_prepare "openid" %}?openid_url=https://www.google.com/accounts/o8/id">Google</a></li>
+    <li><a href="{% url xauth_prepare "openid" %}?openid_url=http://openid.yandex.ru">Yandex</a></li>
+    <li><form method="get" action="{% url xauth_prepare "openid" %}"><label>OpenID:</label><input type="text" name="openid_url" size="20" /><input type="submit" /></form></li>
+    <li><a href="{% url xauth_prepare "oauth" %}?provider=twitter">Twitter</a></li>
 </ul>
 {% endblock %}

File xauth/templates/xauth/openid/prepare.html

-{% extends "xauth/base.html" %}
-{% load i18n %}
-
-{% block xauth_content %}
-<form method="post">
-    {% csrf_token %}
-    {{ form.as_p }}
-    <p class="submit"><input type="submit" /></p>
-    <p>
-        <a href="#" onclick="document.getElementById('id_openid_url').value = 'lorien.name'; return false">lorien.name</a>
-        <a href="#" onclick="document.getElementById('id_openid_url').value = 'https://www.google.com/accounts/o8/id'; return false">google</a>
-    </p>
-</form>
-{% endblock %}

File xauth/views.py

 
     logger.debug('VIEW START exec [service=%s, command=%s]' % (service, command))
     if not service in settings.XAUTH_SERVICES:
-        messages.error(request, u'Invalid service')
+        messages.error(request, u'Invalid xauth service: %s' % service)
         return redirect('xauth_login')
     module = __import__('xauth.service.%s.views' % service, globals(), locals(), ['foo'])
     return getattr(module, command)(request)
             clear_xauth_session(request)
             return redirect(reverse('xauth_login') + qs)
     else:
-        service = request.session['xauth_response']['service']
-        module = __import__('xauth.service.%s.util' % service, globals(), locals(), ['foo'])
-        save_assoc = getattr(module, 'save_association')
+        xresponse = request.session['xauth_response']
+        service = xresponse['service']
 
         if request.user.is_authenticated():
-            save_assoc(request.user, request.session['xauth_response'])
+            XauthAssociation.objects.create(user=request.user, identity=xresponse['identity'],
+                                            service=service)
             messages.success(request, _('You have successfully created new association'))
             clear_xauth_session(request)
             return redirect(get_redirect_url(request, settings.XAUTH_SIGNUP_SUCCESS_URL))
         form = form_class(request.POST or None, instance=user, initial=request.session['xauth_response']['profile'])
         if form.is_valid():
             user = form.save()
-            save_assoc(user, request.session['xauth_response'])
+            XauthAssociation.objects.create(user=user, identity=xresponse['identity'],
+                                            service=service)
             messages.success(request, _('You have been successfuly signed up'))
             # Need to assign `backend` attribute to the user
             user = auth.authenticate(xauth_response=request.session['xauth_response'])