Mercurial > public > sg101
diff 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 |
line wrap: on
line diff
--- a/gcalendar/oauth.py Tue Nov 11 18:27:05 2014 -0600 +++ b/gcalendar/oauth.py Tue Nov 18 21:09:24 2014 -0600 @@ -2,84 +2,113 @@ This module handles the OAuth integration with Google. """ -from __future__ import with_statement +import datetime import logging +import os -import gdata.gauth -from gdata.calendar_resource.client import CalendarResourceClient - +from oauth2client.client import OAuth2WebServerFlow, FlowExchangeError +from oauth2client.file import Storage from django.conf import settings +from django.core.cache import cache logger = logging.getLogger(__name__) -USER_AGENT = 'surfguitar101-gcalendar-v1' -REQ_TOKEN_SESSION_KEY = 'gcalendar oauth request token' +FLOW_CACHE_KEY = 'gcalendar-oauth-flow' +FLOW_CACHE_TIMEOUT = 60 +SCOPE = 'https://www.googleapis.com/auth/calendar' -def fetch_auth(request, scopes, callback_url): +class OAuthError(Exception): + """Base exception for errors raised by the oauth module""" + + +def check_credentials_status(): + """Performs a stat() on the credentials file and returns the last modified + time as a datetime object. If an error occurs, None is returned. """ - This function fetches a request token from Google and stores it in the - session. It then returns the authorization URL as a string. + try: + status = os.stat(settings.GCAL_CREDENTIALS_PATH) + return datetime.datetime.fromtimestamp(status.st_mtime) + except OSError: + return None + + +def get_auth_url(request, callback_url): + """ + This function creates an OAuth flow object and uses it to generate an + authorization URL which is returned to the caller. request - the HttpRequest object for the user requesting the token. The token is stored in the session object attached to this request. - scopes - a list of scope strings that the request token is for. See - http://code.google.com/apis/gdata/faq.html#AuthScopes - callback_url - a string that is the URL that Google should redirect the user to after the user has authorized our application access to their data. - This function only supports RSA-SHA1 authentication. Settings in the Django - settings module determine the consumer key and path to the RSA private key. """ - logger.info("fetch_auth started; callback url='%s'", callback_url) - client = CalendarResourceClient(None, source=USER_AGENT) + logger.info("get_auth_url started; callback url='%s'", callback_url) - with open(settings.GOOGLE_OAUTH_PRIVATE_KEY_PATH, 'r') as f: - rsa_key = f.read() - logger.info("read RSA key; now getting request token") + flow = OAuth2WebServerFlow(client_id=settings.GCAL_CLIENT_ID, + client_secret=settings.GCAL_CLIENT_SECRET, + scope=SCOPE, + redirect_uri=callback_url) - request_token = client.GetOAuthToken( - scopes, - callback_url, - settings.GOOGLE_OAUTH_CONSUMER_KEY, - rsa_private_key=rsa_key) + auth_url = flow.step1_get_authorize_url() + logger.info("auth url: '%s'", auth_url) - logger.info("received token") - request.session[REQ_TOKEN_SESSION_KEY] = request_token + # Save the flow in the cache so we can use it when Google calls the + # callback. The expiration time also lets us check to make sure someone + # isn't spoofing google if we are called at some random time. + # Note: using the session might seem like a more obvious choice, but flow + # objects are not JSON-serializable, and we don't want to use pickelable + # sessions for security reasons. - auth_url = request_token.generate_authorization_url() - logger.info("generated auth url '%s'", str(auth_url)) + cache.set(FLOW_CACHE_KEY, flow, FLOW_CACHE_TIMEOUT) - return str(auth_url) + return auth_url -def get_access_token(request): +def auth_return(request): """ This function should be called after Google has sent the user back to us - after the user authorized us. We retrieve the oauth token from the request - URL and then upgrade it to an access token. We then return the access token. + after the user authorized us. We retrieve the authorization code from the + request query parameters and then exchange it for access tokens. These + tokens are stored in our credentials file. + If an error is encountered, an OAuthError is raised. """ - logger.info("get_access_token called as '%s'", request.get_full_path()) + logger.info("auth_return called as '%s'", request.get_full_path()) - saved_token = request.session.get(REQ_TOKEN_SESSION_KEY) - if saved_token is None: - logger.error("saved request token not found in session!") - return None + # Try to retrieve the flow object from the cache + flow = cache.get(FLOW_CACHE_KEY) + if not flow: + logger.warning("auth_return no flow in cache, bailing out") + raise OAuthError("No flow found. Perhaps we timed out?") - logger.info("extracting token...") - request_token = gdata.gauth.AuthorizeRequestToken(saved_token, - request.build_absolute_uri()) + # Delete flow out of cache to close our operational window + cache.delete(FLOW_CACHE_KEY) - logger.info("upgrading to access token...") + # Process the request + error = request.GET.get('error') + if error: + logging.error("auth_return received error: %s", error) + raise OAuthError("Error authorizing request: %s" % error) - client = CalendarResourceClient(None, source=USER_AGENT) - access_token = client.GetAccessToken(request_token) + code = request.GET.get('code') + if not code: + logging.error("auth_return missing code") + raise OAuthError("No authorization code received") - logger.info("upgraded to access token...") - return access_token + logging.info("auth_return exchanging code for credentials") + try: + credentials = flow.step2_exchange(code) + except FlowExchangeError as ex: + logging.error("auth_return exchange failed: %s", ex) + raise OAuthError(str(ex)) + + logging.info("auth_return storing credentials") + storage = Storage(settings.GCAL_CREDENTIALS_PATH) + storage.put(credentials) + logging.info("auth_return completed successfully") def serialize_token(token):