Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'jespern/master'

Conflicts:
	socialregistration/views.py
  • Loading branch information...
commit 48e1bffac854e7c9916de40a5ac440eb7c1a626f 2 parents 65a9117 + c7197d9
@flashingpumpkin authored
Showing with 265 additions and 35 deletions.
  1. +1 −1  socialregistration/clients/__init__.py
  2. +11 −6 socialregistration/clients/oauth.py
  3. 0  socialregistration/contrib/google/__init__.py
  4. +4 −0 socialregistration/contrib/google/admin.py
  5. +17 −0 socialregistration/contrib/google/auth.py
  6. +53 −0 socialregistration/contrib/google/client.py
  7. +36 −0 socialregistration/contrib/google/models.py
  8. +3 −0  socialregistration/contrib/google/templates/socialregistration/google/google.html
  9. +11 −0 socialregistration/contrib/google/templates/socialregistration/google/google_button.html
  10. 0  socialregistration/contrib/google/templatetags/__init__.py
  11. +6 −0 socialregistration/contrib/google/templatetags/google.py
  12. 0  socialregistration/contrib/google/tests.py
  13. +10 −0 socialregistration/contrib/google/urls.py
  14. +24 −0 socialregistration/contrib/google/views.py
  15. +1 −1  socialregistration/contrib/linkedin/client.py
  16. +1 −1  socialregistration/contrib/openid/client.py
  17. +1 −1  socialregistration/contrib/openid/views.py
  18. +1 −1  socialregistration/contrib/tumblr/client.py
  19. +13 −3 socialregistration/contrib/twitter/client.py
  20. +0 −6 socialregistration/migrations/0003_auto__add_unique_twitterprofile_user__add_unique_openidprofile_user__a.py
  21. +11 −4 socialregistration/mixins.py
  22. +4 −0 socialregistration/urls.py
  23. +57 −11 socialregistration/views.py
View
2  socialregistration/clients/__init__.py
@@ -22,7 +22,7 @@ def get_redirect_url(self, **kwargs):
"""
raise NotImplementedError
- def get_callback_url(self):
+ def get_callback_url(self, **kwargs):
"""
Returns the URL where the service should redirect the user back to
once permissions/access were granted - or not. This should take in
View
17 socialregistration/clients/oauth.py
@@ -1,7 +1,11 @@
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
from socialregistration.clients import Client
+
+from django.conf import settings
+
import httplib2
+import socket
import logging
import oauth2 as oauth
import urllib
@@ -9,6 +13,7 @@
logger = logging.getLogger(__name__)
+TIMEOUT = getattr(settings, 'SOCIALREGISTRATION_SOCKET_TIMEOUT', 5)
class OAuthError(Exception):
"""
@@ -64,19 +69,19 @@ def client(self, verifier=None):
# We're just starting out and don't have neither request nor access
# token. Return the standard client
if not self._request_token and not self._access_token:
- client = oauth.Client(self.consumer)
+ client = oauth.Client(self.consumer, timeout=TIMEOUT)
# We're one step in, we've got the request token and can add that to
# the client.
if self._request_token and not self._access_token:
if verifier is not None:
self._request_token.set_verifier(verifier)
- client = oauth.Client(self.consumer, self._request_token)
+ client = oauth.Client(self.consumer, self._request_token, timeout=TIMEOUT)
# Two steps in, we've got an access token and can now properly sign
# our client requests with it.
if self._access_token:
- client = oauth.Client(self.consumer, self._access_token)
+ client = oauth.Client(self.consumer, self._access_token, timeout=TIMEOUT)
return client
@@ -230,7 +235,8 @@ def __init__(self, access_token=None):
self._access_token = access_token
def client(self):
- return httplib2.Http()
+ ca_certs = getattr(settings, 'HTTPLIB2_CA_CERTS', None)
+ return httplib2.Http(ca_certs=ca_certs, timeout=TIMEOUT)
def get_redirect_url(self, state='', **kwargs):
"""
@@ -342,7 +348,6 @@ def request(self, url, method="GET", params=None, headers=None, is_signed=True):
if method.upper() == "GET":
url = '%s?%s' % (url, urllib.urlencode(params))
return self.client().request(url, method=method, headers=headers)
- return self.client().request(url, method, body=urllib.urlencode(params),
- headers=headers)
+ return self.client().request(url, method, body=urllib.urlencode(params), headers=headers)
View
0  socialregistration/contrib/google/__init__.py
No changes.
View
4 socialregistration/contrib/google/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from socialregistration.contrib.google.models import GoogleProfile
+
+admin.site.register(GoogleProfile)
View
17 socialregistration/contrib/google/auth.py
@@ -0,0 +1,17 @@
+from django.contrib.sites.models import Site
+from socialregistration.contrib.google.models import GoogleProfile
+from django.contrib.auth.backends import ModelBackend
+
+
+class GoogleAuth(ModelBackend):
+ supports_object_permissions = False
+ supports_anonymous_user = False
+
+ def authenticate(self, **kwargs):
+ uid = kwargs.get('google_id')
+ try:
+ return GoogleProfile.objects.get(
+ google_id = uid,
+ site = Site.objects.get_current()).user
+ except GoogleProfile.DoesNotExist:
+ return None
View
53 socialregistration/contrib/google/client.py
@@ -0,0 +1,53 @@
+from django.conf import settings
+from django.contrib.sites.models import Site
+from django.core.urlresolvers import reverse
+from socialregistration.clients.oauth import OAuth2, OAuthError
+from socialregistration.settings import SESSION_KEY
+import json
+import httplib2
+import urllib
+
+class Google(OAuth2):
+ client_id = getattr(settings, 'GOOGLE_CLIENT_ID', '')
+ secret = getattr(settings, 'GOOGLE_CLIENT_SECRET', '')
+ scope = getattr(settings, 'GOOGLE_REQUEST_PERMISSIONS', 'https://www.googleapis.com/auth/userinfo.profile')
+
+ auth_url = 'https://accounts.google.com/o/oauth2/auth'
+ access_token_url = 'https://accounts.google.com/o/oauth2/token'
+
+ _user_info = None
+
+ def get_callback_url(self, **kwargs):
+ if self.is_https():
+ return 'https://%s%s' % (Site.objects.get_current().domain,
+ reverse('socialregistration:google:callback'))
+ return 'http://%s%s' % (Site.objects.get_current().domain,
+ reverse('socialregistration:google:callback'))
+
+ def parse_access_token(self, content):
+ """
+ Forsquare returns JSON instead of url encoded data.
+ """
+ return json.loads(content)
+
+ def request_access_token(self, params):
+ """
+ Google requires correct content-type for POST requests
+ """
+ return self.client().request(self.access_token_url, method="POST", body=urllib.urlencode(params), headers={'Content-Type':'application/x-www-form-urlencoded'})
+
+ def get_access_token(self, **params):
+ """
+ Google requires grant_type
+ """
+ return super(Google, self).get_access_token(grant_type='authorization_code', **params)
+
+ def get_user_info(self):
+ if self._user_info is None:
+ resp, content = self.request('https://www.googleapis.com/oauth2/v1/userinfo', params={'access_token': self._access_token})
+ self._user_info = json.loads(content)
+ return self._user_info
+
+ @staticmethod
+ def get_session_key():
+ return '%sgoogle' % SESSION_KEY
View
36 socialregistration/contrib/google/models.py
@@ -0,0 +1,36 @@
+from django.contrib.auth import authenticate
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.db import models
+from socialregistration.signals import connect
+
+class GoogleProfile(models.Model):
+ user = models.ForeignKey(User, unique=True)
+ site = models.ForeignKey(Site, default=Site.objects.get_current)
+ google_id = models.CharField(max_length = 255)
+
+ def __unicode__(self):
+ try:
+ return u'%s: %s' % (self.user, self.google_id)
+ except User.DoesNotExist:
+ return u'None'
+
+ def authenticate(self):
+ return authenticate(google_id=self.google_id)
+
+class GoogleAccessToken(models.Model):
+ profile = models.OneToOneField(GoogleProfile, related_name='access_token')
+ access_token = models.CharField(max_length=255)
+
+def save_google_token(sender, user, profile, client, **kwargs):
+ try:
+ GoogleAccessToken.objects.get(profile=profile).delete()
+ except GoogleAccessToken.DoesNotExist:
+ pass
+
+ GoogleAccessToken.objects.create(access_token = client.get_access_token(),
+ profile = profile)
+
+
+connect.connect(save_google_token, sender=GoogleProfile,
+ dispatch_uid='socialregistration_google_token')
View
3  socialregistration/contrib/google/templates/socialregistration/google/google.html
@@ -0,0 +1,3 @@
+{% if error %}
+ <p>{{ error }}</p>
+{% endif %}
View
11 socialregistration/contrib/google/templates/socialregistration/google/google_button.html
@@ -0,0 +1,11 @@
+<form action="{% url socialregistration:google:redirect %}" method="post">
+ {% csrf_token %}
+ {% if next %}
+ <input type="hidden" name="next" value="{{ next }}" />
+ {% endif %}
+ {% if button %}
+ <input type="image" onclick="this.form.submit();" src="{{ button }}" />
+ {% else %}
+ <input type="submit" value="Sign in with Google" />
+ {% endif %}
+</form>
View
0  socialregistration/contrib/google/templatetags/__init__.py
No changes.
View
6 socialregistration/contrib/google/templatetags/google.py
@@ -0,0 +1,6 @@
+from django import template
+from socialregistration.templatetags import button
+
+register = template.Library()
+
+register.tag('google_button', button('socialregistration/google/google_button.html'))
View
0  socialregistration/contrib/google/tests.py
No changes.
View
10 socialregistration/contrib/google/urls.py
@@ -0,0 +1,10 @@
+from django.conf import settings
+from django.conf.urls.defaults import *
+from socialregistration.contrib.google.views import GoogleRedirect, \
+ GoogleCallback, GoogleSetup
+
+urlpatterns = patterns('',
+ url('^redirect/$', GoogleRedirect.as_view(), name='redirect'),
+ url('^callback/$', GoogleCallback.as_view(), name='callback'),
+ url('^setup/$', GoogleSetup.as_view(), name='setup'),
+)
View
24 socialregistration/contrib/google/views.py
@@ -0,0 +1,24 @@
+from django.core.urlresolvers import reverse
+from socialregistration.contrib.google.client import Google
+from socialregistration.contrib.google.models import GoogleProfile
+from socialregistration.views import OAuthRedirect, OAuthCallback, SetupCallback
+
+class GoogleRedirect(OAuthRedirect):
+ client = Google
+ template_name = 'socialregistration/google/google.html'
+
+class GoogleCallback(OAuthCallback):
+ client = Google
+ template_name = 'socialregistration/google/google.html'
+
+ def get_redirect(self):
+ return reverse('socialregistration:google:setup')
+
+class GoogleSetup(SetupCallback):
+ client = Google
+ profile = GoogleProfile
+ template_name = 'socialregistration/google/google.html'
+
+ def get_lookup_kwargs(self, request, client):
+ return {'google_id': client.get_user_info()['id']}
+
View
2  socialregistration/contrib/linkedin/client.py
@@ -16,7 +16,7 @@ class LinkedIn(OAuth):
_user_info = None
- def get_callback_url(self):
+ def get_callback_url(self, **kwargs):
if self.is_https():
return urlparse.urljoin(
'https://%s' % Site.objects.get_current().domain,
View
2  socialregistration/contrib/openid/client.py
@@ -17,7 +17,7 @@ def get_realm(self):
return 'https://%s/' % Site.objects.get_current().domain
return 'http://%s/' % Site.objects.get_current().domain
- def get_callback_url(self):
+ def get_callback_url(self, **kwargs):
return urlparse.urljoin(self.get_realm(),
reverse('socialregistration:openid:callback'))
View
2  socialregistration/contrib/openid/views.py
@@ -39,7 +39,7 @@ def get(self, request):
msg = _("Unfortunately we couldn't validate your identity: %s") % client.result.message
else:
msg = _("Unfortunately we couldn't validate your identity")
- return self.render_to_response({'error': msg})
+ return self.error_to_response(request, {'error': msg})
# Save the client back to the session or we're not carrying the result
# to the next view.
View
2  socialregistration/contrib/tumblr/client.py
@@ -14,7 +14,7 @@ class Tumblr(OAuth):
access_token_url = 'http://www.tumblr.com/oauth/access_token'
auth_url = 'http://www.tumblr.com/oauth/authorize'
- def get_callback_url(self):
+ def get_callback_url(self, **kwargs):
if self.is_https():
return urlparse.urljoin(
'https://%s' % Site.objects.get_current().domain,
View
16 socialregistration/contrib/twitter/client.py
@@ -1,9 +1,10 @@
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
-from socialregistration.clients.oauth import OAuth
+from socialregistration.clients.oauth import OAuth, OAuthError
from socialregistration.settings import SESSION_KEY
import urlparse
+import json
class Twitter(OAuth):
api_key = getattr(settings, 'TWITTER_CONSUMER_KEY', '')
@@ -13,7 +14,9 @@ class Twitter(OAuth):
access_token_url = 'https://api.twitter.com/oauth/access_token'
auth_url = 'https://api.twitter.com/oauth/authenticate'
- def get_callback_url(self):
+ info_url = 'https://api.twitter.com/1/account/verify_credentials.json'
+
+ def get_callback_url(self, **kwargs):
if self.is_https():
return urlparse.urljoin(
'https://%s' % Site.objects.get_current().domain,
@@ -23,7 +26,14 @@ def get_callback_url(self):
reverse('socialregistration:twitter:callback'))
def get_user_info(self):
- return self._access_token_dict
+ dct = self._access_token_dict or {}
+
+ try:
+ dct.update(json.loads(self.request(self.info_url)))
+ except OAuthError:
+ pass
+
+ return dct
@staticmethod
def get_session_key():
View
6 ...alregistration/migrations/0003_auto__add_unique_twitterprofile_user__add_unique_openidprofile_user__a.py
@@ -11,9 +11,6 @@ def forwards(self, orm):
# Adding unique constraint on 'TwitterProfile', fields ['user']
db.create_unique('socialregistration_twitterprofile', ['user_id'])
- # Adding unique constraint on 'OpenIDProfile', fields ['user']
- db.create_unique('socialregistration_openidprofile', ['user_id'])
-
# Adding unique constraint on 'FacebookProfile', fields ['user']
db.create_unique('socialregistration_facebookprofile', ['user_id'])
@@ -23,9 +20,6 @@ def backwards(self, orm):
# Removing unique constraint on 'FacebookProfile', fields ['user']
db.delete_unique('socialregistration_facebookprofile', ['user_id'])
- # Removing unique constraint on 'OpenIDProfile', fields ['user']
- db.delete_unique('socialregistration_openidprofile', ['user_id'])
-
# Removing unique constraint on 'TwitterProfile', fields ['user']
db.delete_unique('socialregistration_twitterprofile', ['user_id'])
View
15 socialregistration/mixins.py
@@ -9,7 +9,8 @@
from socialregistration.settings import SESSION_KEY
import urlparse
-
+ERROR_VIEW = getattr(settings, 'SOCIALREGISTRATION_ERROR_VIEW_FUNCTION',
+ None)
class CommonMixin(TemplateResponseMixin):
"""
@@ -60,7 +61,7 @@ def login(self, request, user):
"""
return login(request, user)
- def inactive_response(self):
+ def inactive_response(self, request):
"""
Return an inactive message.
"""
@@ -68,7 +69,7 @@ def inactive_response(self):
if inactive_url:
return HttpResponseRedirect(inactive_url)
else:
- return self.render_to_response({'error': _("This user account is marked as inactive.")})
+ return self.error_to_response(request, {'error': _("This user account is marked as inactive.")})
def redirect(self, request):
"""
@@ -227,8 +228,14 @@ def send_connect_signal(self, request, user, profile, client):
signals.connect.send(sender=profile.__class__, user=user, profile=profile,
client=client, request=request)
+class ErrorMixin(object):
+ def error_to_response(self, request, error_dict, **context):
+ if ERROR_VIEW:
+ return self.import_attribute(ERROR_VIEW)(request, error_dict, **context)
+ return self.render_to_response(error_dict, **context)
+
class SocialRegistration(CommonMixin, ClientMixin, ProfileMixin, SessionMixin,
- SignalMixin):
+ SignalMixin, ErrorMixin):
"""
Combine all mixins into a single class.
"""
View
4 socialregistration/urls.py
@@ -44,6 +44,10 @@
url(r'^instagram/', include('socialregistration.contrib.instagram.urls',
namespace='instagram')))
+if 'socialregistration.contrib.google' in settings.INSTALLED_APPS:
+ urlpatterns = urlpatterns + patterns('',
+ url(r'^google/', include('socialregistration.contrib.google.urls',
+ namespace='google')))
urlpatterns = urlpatterns + patterns('',
url(r'^setup/$', Setup.as_view(), name='setup'),
View
68 socialregistration/views.py
@@ -5,8 +5,13 @@
from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import View, TemplateView
from socialregistration.clients.oauth import OAuthError
+from socialregistration.contrib.openid.client import OpenIDClient
from socialregistration.mixins import SocialRegistration
+
import logging
+import socket
+
+
GENERATE_USERNAME = getattr(settings, 'SOCIALREGISTRATION_GENERATE_USERNAME', False)
@@ -19,8 +24,13 @@
INITAL_DATA_FUNCTION = getattr(settings, 'SOCIALREGISTRATION_INITIAL_DATA_FUNCTION',
None)
-logger = logging.getLogger(__name__)
+CONTEXT_FUNCTION = getattr(settings, 'SOCIALREGISTRATION_SETUP_CONTEXT_FUNCTION',
+ None)
+
+ALLOW_OPENID_SIGNUPS = getattr(settings, 'SOCIALREGISTRATION_ALLOW_OPENID_SIGNUPS',
+ True)
+logger = logging.getLogger(__name__)
class Setup(SocialRegistration, View):
"""
@@ -57,6 +67,21 @@ def get_initial_data(self, request, user, profile, client):
return func(request, user, profile, client)
return {}
+ def get_context(self, request, user, profile, client):
+ """
+ Return additional context for the setup view. The function can
+ be controlled with ``SOCIALREGISTRATION_SETUP_CONTEXT_FUNCTION``.
+
+ :param request: The current request object
+ :param user: The unsaved user object
+ :param profile: The unsaved profile object
+ :param client: The API client
+ """
+ if CONTEXT_FUNCTION:
+ func = self.import_attribute(CONTEXT_FUNCTION)
+ return func(request, user, profile, client)
+ return {}
+
def generate_username_and_redirect(self, request, user, profile, client):
"""
Generate a username and then redirect the user to the correct place.
@@ -94,10 +119,14 @@ def get(self, request):
When signing a new user up - either display a setup form, or
generate the username automatically.
"""
+
+ if request.user.is_authenticated():
+ return HttpResponseRedirect(self.get_next(request))
+
try:
user, profile, client = self.get_session_data(request)
except KeyError:
- return self.render_to_response(dict(
+ return self.error_to_response(request, dict(
error=_("Social profile is missing from your session.")))
if GENERATE_USERNAME:
@@ -105,23 +134,30 @@ def get(self, request):
form = self.get_form()(initial=self.get_initial_data(request, user, profile, client))
- return self.render_to_response(dict(form=form))
+ additional_context = self.get_context(request, user, profile, client)
+ return self.render_to_response(dict({'form': form}, **additional_context))
def post(self, request):
"""
Save the user and profile, login and send the right signals.
"""
+
+ if request.user.is_authenticated():
+ return self.error_to_response(request, dict(
+ error=_("You are already logged in.")))
+
try:
user, profile, client = self.get_session_data(request)
except KeyError:
- return self.render_to_response(dict(
+ return self.error_to_response(request, dict(
error=_("A social profile is missing from your session.")))
form = self.get_form()(request.POST, request.FILES,
initial=self.get_initial_data(request, user, profile, client))
if not form.is_valid():
- return self.render_to_response(dict(form=form))
+ additional_context = self.get_context(request, user, profile, client)
+ return self.render_to_response(dict({'form': form}, **additional_context))
user, profile = form.save(request, user, profile, client)
@@ -176,7 +212,10 @@ def post(self, request):
try:
return HttpResponseRedirect(url)
except OAuthError, error:
- return self.render_to_response({'error': error})
+ return self.error_to_response(request, {'error': error})
+ except socket.timeout:
+ return self.error_to_response(request, {'error':
+ _('Could not connect to service (timed out)')})
class OAuthCallback(SocialRegistration, View):
@@ -218,9 +257,12 @@ def get(self, request):
request.session[self.get_client().get_session_key()] = client
return HttpResponseRedirect(self.get_redirect())
except KeyError:
- return self.render_to_response({'error': "Session expired."})
+ return self.error_to_response(request, {'error': "Session expired."})
except OAuthError, error:
- return self.render_to_response({'error': error})
+ return self.error_to_response(request, {'error': error})
+ except socket.timeout:
+ return self.error_to_response(request, {'error':
+ _('Could not connect to service (timed out)')})
class SetupCallback(SocialRegistration, TemplateView):
"""
@@ -246,7 +288,7 @@ def get(self, request):
try:
client = request.session[self.get_client().get_session_key()]
except KeyError:
- return self.render_to_response({'error': "Session expired."})
+ return self.error_to_response(request, {'error': "Session expired."})
# Get the lookup dictionary to find the user's profile
lookup_kwargs = self.get_lookup_kwargs(request, client)
@@ -259,7 +301,7 @@ def get(self, request):
# Make sure that there is only *one* account per profile.
if not profile.user == request.user:
self.delete_session_data(request)
- return self.render_to_response({
+ return self.error_to_response(request, {
'error': _('This profile is already connected to another user account.')
})
@@ -279,6 +321,10 @@ def get(self, request):
# No user existing - create a new one and redirect to the final setup view
if user is None:
+ if not ALLOW_OPENID_SIGNUPS and self.client is OpenIDClient:
+ return self.error_to_response(request, {
+ 'error': _('We are not currently accepting new OpenID signups.')
+ })
user = self.create_user()
profile = self.create_profile(user, **lookup_kwargs)
@@ -290,7 +336,7 @@ def get(self, request):
# Inactive user - displaying / redirect to the appropriate place.
if not user.is_active:
- return self.inactive_response()
+ return self.inactive_response(request)
# Active user with existing profile: login, send signal and redirect
self.login(request, user)
Please sign in to comment.
Something went wrong with that request. Please try again.