Skip to content

Commit

Permalink
Added support for Google+
Browse files Browse the repository at this point in the history
  • Loading branch information
subhranath committed Sep 18, 2011
1 parent 340ef0a commit 1daa0c1
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 3 deletions.
11 changes: 11 additions & 0 deletions README
Expand Up @@ -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'.
Binary file modified django_custom_auths.db
Binary file not shown.
3 changes: 2 additions & 1 deletion facebook/views.py
Expand Up @@ -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

Expand Down
Empty file added googleplus/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions googleplus/backends.py
@@ -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
15 changes: 15 additions & 0 deletions googleplus/models.py
@@ -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)

16 changes: 16 additions & 0 deletions googleplus/tests.py
@@ -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)
5 changes: 5 additions & 0 deletions googleplus/urls.py
@@ -0,0 +1,5 @@
from django.conf.urls.defaults import patterns, include, url

urlpatterns = patterns('',
url(r'login/$', 'googleplus.views.login_handler'),
)
22 changes: 22 additions & 0 deletions googleplus/utils.py
@@ -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
119 changes: 119 additions & 0 deletions googleplus/views.py
@@ -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
9 changes: 7 additions & 2 deletions settings.py
Expand Up @@ -123,6 +123,7 @@
# 'django.contrib.admindocs',
'account',
'facebook',
'googleplus',
)

# A sample logging configuration. The only tangible logging
Expand Down Expand Up @@ -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 = ''
1 change: 1 addition & 0 deletions templates/account/index.html
Expand Up @@ -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 %}
1 change: 1 addition & 0 deletions urls.py
Expand Up @@ -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.