Skip to content

Commit

Permalink
Support Google's verified_email (ticket #145)
Browse files Browse the repository at this point in the history
  • Loading branch information
pennersr committed Jan 7, 2013
1 parent 15e281e commit 51f6727
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 20 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2013-01-07 Raymond Penners <[email protected]>

* google: support for Google's `verified_email` flag to determine
whether or not to send confirmation e-mails.

2012-12-22 Raymond Penners <[email protected]>

* socialaccount: Added support for Stack Exchange.
Expand Down
22 changes: 20 additions & 2 deletions allauth/socialaccount/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
from django.utils import simplejson

import allauth.app_settings
from allauth.utils import get_login_redirect_url
from allauth.account import app_settings as account_settings
from allauth.utils import (get_login_redirect_url,
valid_email_or_none)
from allauth.account.adapter import get_adapter
from allauth.account.models import EmailAddress

import providers
from fields import JSONField
Expand Down Expand Up @@ -122,14 +125,18 @@ class SocialLogin(object):
url (e.g. OAuth2 `state` parameter) -- do not put any secrets in
there. It currently only contains the url to redirect to after
login.
`email_addresses` (list of `EmailAddress`): Optional list of
e-mail addresses retrieved from the provider.
"""

def __init__(self, account, token=None):
def __init__(self, account, token=None, email_addresses=[]):
if token:
assert token.account is None or token.account == account
token.account = account
self.token = token
self.account = account
self.email_addresses = email_addresses
self.state = {}

def save(self):
Expand All @@ -140,6 +147,17 @@ def save(self):
if self.token:
self.token.account = self.account
self.token.save()
for email_address in self.email_addresses:
# Pick up only valid ones...
email = valid_email_or_none(email_address.email)
if not email:
continue
# ... and non-conflicting ones...
if (account_settings.UNIQUE_EMAIL
and EmailAddress.objects.filter(email__iexact=email).exists()):
continue
email_address.user = user
email_address.save()

@property
def is_existing(self):
Expand Down
51 changes: 51 additions & 0 deletions allauth/socialaccount/providers/google/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from django.test.utils import override_settings

from allauth.socialaccount.tests import create_oauth2_tests
from allauth.account import app_settings as account_settings
from allauth.account.models import EmailConfirmation, EmailAddress
from allauth.socialaccount.providers import registry
from allauth.tests import MockedResponse

from provider import GoogleProvider

class GoogleTests(create_oauth2_tests(registry.by_id(GoogleProvider.id))):

def get_mocked_response(self, verified_email=True):
return MockedResponse(200, """
{"family_name": "Penners", "name": "Raymond Penners",
"picture": "https://lh5.googleusercontent.com/-GOFYGBVOdBQ/AAAAAAAAAAI/AAAAAAAAAGM/WzRfPkv4xbo/photo.jpg",
"locale": "nl", "gender": "male",
"email": "[email protected]",
"link": "https://plus.google.com/108204268033311374519",
"given_name": "Raymond", "id": "108204268033311374519",
"verified_email": %s }
""" % (repr(verified_email).lower()))

@override_settings(SOCIALACCOUNT_AUTO_SIGNUP=True,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_VERIFICATION
=account_settings.EmailVerificationMethod.MANDATORY)
def test_email_verified(self):
test_email = '[email protected]'
self.login(self.get_mocked_response(verified_email=True))
EmailAddress.objects \
.get(email=test_email,
verified=True)
self.assertFalse(EmailConfirmation.objects \
.filter(email_address__email=test_email) \
.exists())

@override_settings(SOCIALACCOUNT_AUTO_SIGNUP=True,
ACCOUNT_SIGNUP_FORM_CLASS=None,
ACCOUNT_EMAIL_VERIFICATION
=account_settings.EmailVerificationMethod.MANDATORY)
def test_email_unverified(self):
test_email = '[email protected]'
self.login(self.get_mocked_response(verified_email=False))
email_address = EmailAddress.objects \
.get(email=test_email)
self.assertFalse(email_address.verified)
self.assertTrue(EmailConfirmation.objects \
.filter(email_address__email=test_email) \
.exists())

9 changes: 8 additions & 1 deletion allauth/socialaccount/providers/google/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import requests

from allauth.account.models import EmailAddress
from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter,
OAuth2LoginView,
OAuth2CallbackView)
Expand Down Expand Up @@ -37,11 +38,17 @@ def complete_login(self, request, app, token):
user = User(email=extra_data.get('email', ''),
last_name=extra_data.get('family_name', ''),
first_name=extra_data.get('given_name', ''))
email_addresses = []
if user.email and extra_data.get('verified_email'):
email_addresses.append(EmailAddress(email=user.email,
verified=True,
primary=True))
account = SocialAccount(extra_data=extra_data,
uid=uid,
provider=self.provider_id,
user=user)
return SocialLogin(account)
return SocialLogin(account,
email_addresses=email_addresses)

oauth2_login = OAuth2LoginView.adapter_view(GoogleOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(GoogleOAuth2Adapter)
Expand Down
35 changes: 18 additions & 17 deletions allauth/socialaccount/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@
mocked_oauth_responses = {
'github': MockedResponse(200, """
{"type":"User","organizations_url":"https://api.github.com/users/pennersr/orgs","gists_url":"https://api.github.com/users/pennersr/gists{/gist_id}","received_events_url":"https://api.github.com/users/pennersr/received_events","gravatar_id":"8639768262b8484f6a3380f8db2efa5b","followers":16,"blog":"http://www.intenct.info","avatar_url":"https://secure.gravatar.com/avatar/8639768262b8484f6a3380f8db2efa5b?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","login":"pennersr","created_at":"2010-02-10T12:50:51Z","company":"IntenCT","subscriptions_url":"https://api.github.com/users/pennersr/subscriptions","public_repos":14,"hireable":false,"url":"https://api.github.com/users/pennersr","public_gists":0,"starred_url":"https://api.github.com/users/pennersr/starred{/owner}{/repo}","html_url":"https://github.com/pennersr","location":"The Netherlands","bio":null,"name":"Raymond Penners","repos_url":"https://api.github.com/users/pennersr/repos","followers_url":"https://api.github.com/users/pennersr/followers","id":201022,"following":0,"email":"[email protected]","events_url":"https://api.github.com/users/pennersr/events{/privacy}","following_url":"https://api.github.com/users/pennersr/following"}
"""),
'google': MockedResponse(200, """
{"family_name": "Penners", "name": "Raymond Penners",
"picture": "https://lh5.googleusercontent.com/-GOFYGBVOdBQ/AAAAAAAAAAI/AAAAAAAAAGM/WzRfPkv4xbo/photo.jpg",
"locale": "nl", "gender": "male",
"email": "[email protected]",
"link": "https://plus.google.com/108204268033311374519",
"given_name": "Raymond", "id": "108204268033311374519",
"verified_email": true}
"""),
'facebook': MockedResponse(200, """
{
Expand Down Expand Up @@ -69,35 +60,45 @@ def setUp(self):

@override_settings(SOCIALACCOUNT_AUTO_SIGNUP=False)
def test_login(self):
resp_mock = mocked_oauth_responses.get(self.provider.id)
if not resp_mock:
warnings.warn("Cannot test provider %s, no oauth mock"
% self.provider.id)
return
resp = self.login(resp_mock)
self.assertRedirects(resp, reverse('socialaccount_signup'))

def login(self, resp_mock):
resp = self.client.get(reverse(self.provider.id + '_login'))
p = urlparse.urlparse(resp['location'])
q = urlparse.parse_qs(p.query)
complete_url = reverse(self.provider.id+'_callback')
self.assertGreater(q['redirect_uri'][0]
.find(complete_url), 0)
resp_mock = mocked_oauth_responses.get(self.provider.id)
if not resp_mock:
warnings.warn("Cannot test provider %s, no oauth mock"
% self.provider.id)
return
with mocked_response(MockedResponse(200,
'{"access_token":"testac"}',
{'content-type':
'application/json'}),
resp_mock):
resp = self.client.get(complete_url,
{ 'code': 'test' })
self.assertRedirects(resp, reverse('socialaccount_signup'))
return resp




impl = { 'setUp': setUp,
'login': login,
'test_login': test_login }
class_name = 'OAuth2Tests_'+provider.id
Class = type(class_name, (TestCase,), impl)
globals()[class_name] = Class
Class.provider = provider
return Class

# FIXME: Move tests to provider specific app (as has been done for Google)
for provider in providers.registry.get_list():
if isinstance(provider,OAuth2Provider):
create_oauth2_tests(provider)
if provider.id != 'google':
Class = create_oauth2_tests(provider)
globals()[Class.__name__] = Class

0 comments on commit 51f6727

Please sign in to comment.