Permalink
Browse files

Added support for Google+

  • Loading branch information...
subhranath committed Sep 18, 2011
1 parent 340ef0a commit 1daa0c1a3054ae71d9197faebca1f443b0395e32
View
11 README
@@ -14,8 +14,19 @@ Currently supported, simultaneous auth backends:
-------------------------------------------------
- Usual Django login
- Login with Facebook (using Facebook Graph API)
+- Login with Google+
Installing (Tryout)
-------------------
Just clone this repo and hit 'runserver', to try this out.
+For Facebook app:
+i) Create a facebook app from the facebook developers site (with redirect_uri as '/facebook/login/')
+ii) Update settings.py with 'FACEBOOK_APP_ID' and 'FACEBOOK_APP_SECRET'.
+
+For Google+ app:
+i) Create a new application from the Google APIs Console (https://code.google.com/apis/console/),
+with redirect uri specified to '/googleplus/login/' of your server.
+ii) Use the 'client_id' and 'client_secret' obtained from step i, and make
+appropriate changes in settings.py to update 'GOOGLEPLUS_CLIENT_ID' and
+'GOOGLEPLUS_CLIENT_SECRET'.
View
Binary file not shown.
View
@@ -91,7 +91,8 @@ def _create_or_update_facebook_user(profile, access_token, expires):
except FacebookUser.DoesNotExist:
user = User.objects.create( \
first_name=profile['first_name'],
- last_name=profile['last_name']
+ last_name=profile['last_name'],
+ username='fb_' + profile['id']
)
user_is_created = True
View
No changes.
View
@@ -0,0 +1,16 @@
+from django.contrib.auth.models import User
+
+class GooglePlusBackend:
+ supports_inactive_user = False
+
+ def authenticate(self, googleplus_user=None):
+ if googleplus_user is not None:
+ return googleplus_user.user
+ else:
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(id=user_id)
+ except User.DoesNotExist:
+ return None
View
@@ -0,0 +1,15 @@
+from django.contrib.auth.models import User
+from django.db import models
+
+class GooglePlusUser(models.Model):
+ """Stores Google+ user specific details.
+ """
+ user = models.OneToOneField(User)
+ access_token = models.TextField(db_index=True)
+ expiry_at = models.DateTimeField(null=True)
+ googleplus_id = models.CharField(max_length=100, unique=True, db_index=True)
+ googleplus_display_name = models.CharField(max_length=100, unique=True, db_index=True)
+
+ def __unicode__(self):
+ return unicode(self.id) + u' | ' + unicode(self.googleplus_id)
+
View
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
View
@@ -0,0 +1,5 @@
+from django.conf.urls.defaults import patterns, include, url
+
+urlpatterns = patterns('',
+ url(r'login/$', 'googleplus.views.login_handler'),
+)
View
@@ -0,0 +1,22 @@
+import json
+import urllib
+import urlparse
+
+def graph_api(path, params, method="GET"):
+ """Invokes the Google+ API.
+ Returns the response as python dictionary.
+ """
+ # Construct the API url.
+ base_url = 'https://www.googleapis.com/plus/v1/'
+ url = urlparse.urljoin(base_url, path)
+
+ # Invoke the Google+ API with the specified method.
+ if method.upper() == "GET":
+ req = urllib.urlopen(url + '?' + urllib.urlencode(params))
+ elif method.upper() == "POST":
+ req = urllib.urlopen(url, urllib.urlencode(params))
+
+ response = req.read()
+ response_dict = json.loads(response)
+
+ return response_dict
View
@@ -0,0 +1,119 @@
+from django.conf import settings
+from django.contrib import messages
+from django.contrib.auth import authenticate, login
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+import datetime
+import json
+import urllib
+import urlparse
+
+from googleplus import utils
+from googleplus.models import GooglePlusUser
+
+GOOGLEPLUS_LOGIN_URL = '/googleplus/login/'
+REDIRECT_URI = urlparse.urljoin( \
+ 'http://' + Site.objects.get_current().domain, GOOGLEPLUS_LOGIN_URL
+)
+
+def login_handler(request):
+ """Google+ OAuth2 login handler.
+ """
+ if request.user.is_authenticated():
+ return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
+
+ if 'error' in request.GET:
+ messages.add_message(request, messages.ERROR,
+ request.GET['error'])
+ return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
+ elif 'code' in request.GET:
+ params = { \
+ 'client_id': settings.GOOGLEPLUS_CLIENT_ID, \
+ 'redirect_uri': REDIRECT_URI, \
+ 'client_secret': settings.GOOGLEPLUS_CLIENT_SECRET, \
+ 'code': request.GET['code'], \
+ 'grant_type': 'authorization_code', \
+ }
+ req = urllib.urlopen('https://accounts.google.com/o/oauth2/token',
+ urllib.urlencode(params)
+ )
+ if req.getcode() != 200:
+ response = render_to_response('500.html', {}, \
+ context_instance=RequestContext(request))
+ response.status_code = 500
+ return response
+
+ response = req.read()
+ response_query_dict = json.loads(response)
+ access_token = response_query_dict['access_token']
+ expires_in = response_query_dict['expires_in']
+
+ profile = utils.graph_api('people/me', {'access_token': access_token})
+
+ googleplus_user = _create_or_update_googleplus_user(profile, access_token, expires_in)
+
+ user = authenticate(googleplus_user=googleplus_user)
+ if user is not None:
+ if user.is_active:
+ login(request, user)
+ request.session.set_expiry(googleplus_user.expiry_at)
+ if 'next' in request.GET:
+ return HttpResponseRedirect(request.GET['next'])
+ return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
+ else:
+ messages.add_message(request, messages.ERROR, "Account disabled.")
+ else:
+ messages.add_message(request, messages.ERROR, "Login failed.")
+ else:
+ params = { \
+ 'client_id': settings.GOOGLEPLUS_CLIENT_ID, \
+ 'redirect_uri': REDIRECT_URI, \
+ 'scope': 'https://www.googleapis.com/auth/plus.me', \
+ 'response_type': 'code', \
+ }
+ return HttpResponseRedirect('https://accounts.google.com/o/oauth2/auth?' +
+ urllib.urlencode(params)
+ )
+
+def _create_or_update_googleplus_user(profile, access_token, expires_in):
+ """Creates or updates a Google+ user profile in local database.
+ """
+ user_is_created = False
+ try:
+ googleplus_user = GooglePlusUser.objects.get(googleplus_id=profile['id'])
+ except GooglePlusUser.DoesNotExist:
+ first_name, last_name = _get_first_and_last_name(profile['displayName'])
+ user = User.objects.create( \
+ first_name=first_name,
+ last_name=last_name,
+ username='googleplus_' + profile['id']
+ )
+ user_is_created = True
+
+ if user_is_created:
+ googleplus_user = GooglePlusUser()
+ googleplus_user.googleplus_id = profile['id']
+ googleplus_user.user = user
+ else:
+ first_name, last_name = _get_first_and_last_name(profile['displayName'])
+ googleplus_user.user.first_name = first_name
+ googleplus_user.last_name = last_name
+
+ googleplus_user.googleplus_display_name = profile['displayName']
+ googleplus_user.access_token = access_token
+ googleplus_user.expiry_at = datetime.datetime.now() + \
+ datetime.timedelta(seconds=int(expires_in))
+ googleplus_user.save()
+
+ return googleplus_user
+
+def _get_first_and_last_name(display_name):
+ try:
+ first_name, last_name = display_name.strip().rsplit(' ', 1)
+ except ValueError:
+ first_name = display_name
+ last_name = ''
+ return first_name, last_name
View
@@ -123,6 +123,7 @@
# 'django.contrib.admindocs',
'account',
'facebook',
+ 'googleplus',
)
# A sample logging configuration. The only tangible logging
@@ -158,11 +159,15 @@
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'facebook.backends.FacebookBackend',
+ 'googleplus.backends.GooglePlusBackend',
)
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'
-FACEBOOK_APP_ID = '222026897833284'
-FACEBOOK_APP_SECRET = '175712afcb64656ca488844202394e0b'
+FACEBOOK_APP_ID = ''
+FACEBOOK_APP_SECRET = ''
FACEBOOK_APP_PERMISSIONS = ''
+
+GOOGLEPLUS_CLIENT_ID = ''
+GOOGLEPLUS_CLIENT_SECRET = ''
@@ -20,6 +20,7 @@ <h3>You are not authenticated. Login to proceed.</h3>
<div class="login">
<div class="login-option"><a href="{% url 'account.views.login_handler' %}">Usual Login</div>
<div class="login-option"><a href="{% url 'facebook.views.login_handler' %}">Login using Facebook</div>
+ <div class="login-option"><a href="{% url 'googleplus.views.login_handler' %}">Login with Google+</div>
</div>
{% endif %}
{% endblock content %}
View
@@ -18,4 +18,5 @@
url(r'^login/$', 'account.views.login_handler'),
url(r'^logout/$', 'account.views.logout_handler'),
url(r'^facebook/', include('facebook.urls')),
+ url(r'^googleplus/', include('googleplus.urls')),
)

0 comments on commit 1daa0c1

Please sign in to comment.