annotate gcalendar/oauth.py @ 855:8743c566f712

WIP commit for converting to Google Calendar v3 API. This code should be enough to receive tokens from Google. See issue #80.
author Brian Neal <bgneal@gmail.com>
date Tue, 18 Nov 2014 21:09:24 -0600
parents ee87ea74d46b
children 9165edfb1709
rev   line source
bgneal@451 1 """
bgneal@451 2 This module handles the OAuth integration with Google.
bgneal@451 3
bgneal@451 4 """
bgneal@855 5 import datetime
bgneal@451 6 import logging
bgneal@855 7 import os
bgneal@451 8
bgneal@855 9 from oauth2client.client import OAuth2WebServerFlow, FlowExchangeError
bgneal@855 10 from oauth2client.file import Storage
bgneal@451 11 from django.conf import settings
bgneal@855 12 from django.core.cache import cache
bgneal@451 13
bgneal@451 14
bgneal@451 15 logger = logging.getLogger(__name__)
bgneal@855 16 FLOW_CACHE_KEY = 'gcalendar-oauth-flow'
bgneal@855 17 FLOW_CACHE_TIMEOUT = 60
bgneal@855 18 SCOPE = 'https://www.googleapis.com/auth/calendar'
bgneal@451 19
bgneal@451 20
bgneal@855 21 class OAuthError(Exception):
bgneal@855 22 """Base exception for errors raised by the oauth module"""
bgneal@855 23
bgneal@855 24
bgneal@855 25 def check_credentials_status():
bgneal@855 26 """Performs a stat() on the credentials file and returns the last modified
bgneal@855 27 time as a datetime object. If an error occurs, None is returned.
bgneal@451 28 """
bgneal@855 29 try:
bgneal@855 30 status = os.stat(settings.GCAL_CREDENTIALS_PATH)
bgneal@855 31 return datetime.datetime.fromtimestamp(status.st_mtime)
bgneal@855 32 except OSError:
bgneal@855 33 return None
bgneal@855 34
bgneal@855 35
bgneal@855 36 def get_auth_url(request, callback_url):
bgneal@855 37 """
bgneal@855 38 This function creates an OAuth flow object and uses it to generate an
bgneal@855 39 authorization URL which is returned to the caller.
bgneal@451 40
bgneal@451 41 request - the HttpRequest object for the user requesting the token. The
bgneal@451 42 token is stored in the session object attached to this request.
bgneal@451 43
bgneal@451 44 callback_url - a string that is the URL that Google should redirect the user
bgneal@451 45 to after the user has authorized our application access to their data.
bgneal@451 46
bgneal@451 47 """
bgneal@855 48 logger.info("get_auth_url started; callback url='%s'", callback_url)
bgneal@451 49
bgneal@855 50 flow = OAuth2WebServerFlow(client_id=settings.GCAL_CLIENT_ID,
bgneal@855 51 client_secret=settings.GCAL_CLIENT_SECRET,
bgneal@855 52 scope=SCOPE,
bgneal@855 53 redirect_uri=callback_url)
bgneal@451 54
bgneal@855 55 auth_url = flow.step1_get_authorize_url()
bgneal@855 56 logger.info("auth url: '%s'", auth_url)
bgneal@451 57
bgneal@855 58 # Save the flow in the cache so we can use it when Google calls the
bgneal@855 59 # callback. The expiration time also lets us check to make sure someone
bgneal@855 60 # isn't spoofing google if we are called at some random time.
bgneal@855 61 # Note: using the session might seem like a more obvious choice, but flow
bgneal@855 62 # objects are not JSON-serializable, and we don't want to use pickelable
bgneal@855 63 # sessions for security reasons.
bgneal@451 64
bgneal@855 65 cache.set(FLOW_CACHE_KEY, flow, FLOW_CACHE_TIMEOUT)
bgneal@451 66
bgneal@855 67 return auth_url
bgneal@451 68
bgneal@451 69
bgneal@855 70 def auth_return(request):
bgneal@451 71 """
bgneal@451 72 This function should be called after Google has sent the user back to us
bgneal@855 73 after the user authorized us. We retrieve the authorization code from the
bgneal@855 74 request query parameters and then exchange it for access tokens. These
bgneal@855 75 tokens are stored in our credentials file.
bgneal@451 76
bgneal@855 77 If an error is encountered, an OAuthError is raised.
bgneal@451 78 """
bgneal@855 79 logger.info("auth_return called as '%s'", request.get_full_path())
bgneal@451 80
bgneal@855 81 # Try to retrieve the flow object from the cache
bgneal@855 82 flow = cache.get(FLOW_CACHE_KEY)
bgneal@855 83 if not flow:
bgneal@855 84 logger.warning("auth_return no flow in cache, bailing out")
bgneal@855 85 raise OAuthError("No flow found. Perhaps we timed out?")
bgneal@451 86
bgneal@855 87 # Delete flow out of cache to close our operational window
bgneal@855 88 cache.delete(FLOW_CACHE_KEY)
bgneal@451 89
bgneal@855 90 # Process the request
bgneal@855 91 error = request.GET.get('error')
bgneal@855 92 if error:
bgneal@855 93 logging.error("auth_return received error: %s", error)
bgneal@855 94 raise OAuthError("Error authorizing request: %s" % error)
bgneal@451 95
bgneal@855 96 code = request.GET.get('code')
bgneal@855 97 if not code:
bgneal@855 98 logging.error("auth_return missing code")
bgneal@855 99 raise OAuthError("No authorization code received")
bgneal@451 100
bgneal@855 101 logging.info("auth_return exchanging code for credentials")
bgneal@855 102 try:
bgneal@855 103 credentials = flow.step2_exchange(code)
bgneal@855 104 except FlowExchangeError as ex:
bgneal@855 105 logging.error("auth_return exchange failed: %s", ex)
bgneal@855 106 raise OAuthError(str(ex))
bgneal@855 107
bgneal@855 108 logging.info("auth_return storing credentials")
bgneal@855 109 storage = Storage(settings.GCAL_CREDENTIALS_PATH)
bgneal@855 110 storage.put(credentials)
bgneal@855 111 logging.info("auth_return completed successfully")
bgneal@458 112
bgneal@458 113
bgneal@458 114 def serialize_token(token):
bgneal@458 115 """
bgneal@458 116 This function turns a token into a string and returns it.
bgneal@458 117
bgneal@458 118 """
bgneal@458 119 return gdata.gauth.TokenToBlob(token)
bgneal@458 120
bgneal@458 121
bgneal@458 122 def deserialize_token(s):
bgneal@458 123 """
bgneal@458 124 This function turns a string into a token returns it. The string must have
bgneal@458 125 previously been created with serialize_token().
bgneal@458 126
bgneal@458 127 """
bgneal@458 128 return gdata.gauth.TokenFromBlob(s)