From c408c7c8a4f296d4538d17dd38ef3cebd12ea3cc Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 14 May 2014 16:32:10 +0000 Subject: [PATCH] defaulting to google oauth where possible --- django_mailbox/transports/gmail.py | 51 ++++++---- django_mailbox/transports/google_api.py | 126 ++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 django_mailbox/transports/google_api.py diff --git a/django_mailbox/transports/gmail.py b/django_mailbox/transports/gmail.py index a5a31ee..a5c613c 100644 --- a/django_mailbox/transports/gmail.py +++ b/django_mailbox/transports/gmail.py @@ -19,27 +19,42 @@ class GmailTransport(EmailTransport): self.port = 143 def connect(self, username, password): - self.server = self.transport(self.hostname, self.port) - typ, msg = self.server.login(username, password) - self.server.select() + # Try to use oauth2 first. It's much safer + try: + self._connect_oauth(username) + except (TypeError, ValueError), e: + print " Couldn't do oauth2", e + self.server = self.transport(self.hostname, self.port) + typ, msg = self.server.login(username, password) + self.server.select() - def _connect_oauth(self, username, refresh_token): - import oauth2 - client_id = "REDACTED" - client_secret = "REDACTED" - response = oauth2.RefreshToken(client_id, client_secret, refresh_token) - access_token = response['access_token'] - print "New Access Token :", access_token - print "Expires in :", response['expires_in'] - # before passing into IMAPLib access token needs to be converted - # into a string - oauth2String = oauth2.GenerateOAuth2String( - username, - access_token, - base64_encode=False + def _connect_oauth(self, username): + # username should be an email address that has already been authorized + # for gmail access + try: + from google_api import ( + get_google_access_token, + fetch_user_info) + + except ImportError: + raise ValueError( + "Install python-social-auth to use oauth2 auth for gmail" + ) + + try: + access_token = get_google_access_token(username) + google_email_address = fetch_user_info(username)[u'email'] + except TypeError: + # Sometimes we have to try again + access_token = get_google_access_token(username) + google_email_address = fetch_user_info(username)[u'email'] + + auth_string = 'user=%s\1auth=Bearer %s\1\1' % ( + google_email_address, + access_token ) self.server = self.transport(self.hostname, self.port) - self.server.authenticate('XOAUTH2', lambda x: oauth2String) + self.server.authenticate('XOAUTH2', lambda x: auth_string) self.server.select() def _get_all_message_ids(self): diff --git a/django_mailbox/transports/google_api.py b/django_mailbox/transports/google_api.py new file mode 100644 index 0000000..9900b70 --- /dev/null +++ b/django_mailbox/transports/google_api.py @@ -0,0 +1,126 @@ +from social.apps.django_app.default.models import UserSocialAuth +import requests +from django.conf import settings + + +class AccessTokenNotFound(Exception): + pass + + +class RefreshTokenNotFound(Exception): + pass + + +def get_google_consumer_key(): + return settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY + + +def get_google_consumer_secret(): + return settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET + + +def get_google_access_token(email): + # TODO: This should be cacheable + try: + me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2") + return me.extra_data['access_token'] + except (UserSocialAuth.DoesNotExist, KeyError): + raise AccessTokenNotFound + + +def update_google_extra_data(email, extra_data): + try: + me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2") + me.extra_data = extra_data + me.save() + except (UserSocialAuth.DoesNotExist, KeyError): + raise AccessTokenNotFound + + +def get_google_refresh_token(email): + try: + me = UserSocialAuth.objects.get(uid=email, provider="google-oauth2") + return me.extra_data['refresh_token'] + except (UserSocialAuth.DoesNotExist, KeyError): + raise RefreshTokenNotFound + + +def google_api_get(email, url): + headers = dict( + Authorization="Bearer %s" % get_google_access_token(email), + ) + r = requests.get(url, headers=headers) + print "I got a %s" % r.status_code + if r.status_code == 401: + # Go use the refresh token + refresh_authorization(email) + r = requests.get(url, headers=headers) + print "I got a %s" % r.status_code + if r.status_code == 200: + try: + return r.json() + except ValueError: + print "returning text" + return r.text + + +def google_api_post(email, url, post_data, authorized=True): + # TODO: Make this a lot less ugly. especially the 401 handling + headers = dict() + if authorized is True: + headers.update(dict( + Authorization="Bearer %s" % get_google_access_token(email), + )) + r = requests.post(url, headers=headers, data=post_data) + if r.status_code == 401: + refresh_authorization(email) + r = requests.post(url, headers=headers, data=post_data) + if r.status_code == 200: + try: + return r.json() + except ValueError: + print "returning text" + return r.text + + +def refresh_authorization(email): + refresh_token = get_google_refresh_token(email) + post_data = dict( + refresh_token=refresh_token, + client_id=get_google_consumer_key(), + client_secret=get_google_consumer_secret(), + grant_type='refresh_token', + ) + results = google_api_post( + email, + "https://accounts.google.com/o/oauth2/token?access_type=offline", + post_data, + authorized=False) + results.update({'refresh_token': refresh_token}) + update_google_extra_data(email, results) + + +def fetch_user_info(email): + result = google_api_get( + email, + "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" + ) + return result + + +def fetch_google_contacts(email, limit=10000): + result = google_api_get( + email, + "https://www.google.com/m8/feeds/contacts/default/full?v=3.0&alt=json&max-results=%s" % limit + ) + entries = result['feed']['entry'] + valid_entries = [x for x in entries if u'gd$email' in x.keys() and u'gd$name' in x.keys()] + contacts = [] + for each in valid_entries: + try: + name = each[u'gd$name'][u'gd$fullName'][u'$t'] + except KeyError: + name = None + for each_email in each[u'gd$email']: + contacts.append(dict(name=name, email=each_email[u'address'])) + return contacts