Mercurial > public > sg101
comparison 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 |
comparison
equal
deleted
inserted
replaced
854:1583a41511cc | 855:8743c566f712 |
---|---|
1 """ | 1 """ |
2 This module handles the OAuth integration with Google. | 2 This module handles the OAuth integration with Google. |
3 | 3 |
4 """ | 4 """ |
5 from __future__ import with_statement | 5 import datetime |
6 import logging | 6 import logging |
7 import os | |
7 | 8 |
8 import gdata.gauth | 9 from oauth2client.client import OAuth2WebServerFlow, FlowExchangeError |
9 from gdata.calendar_resource.client import CalendarResourceClient | 10 from oauth2client.file import Storage |
10 | |
11 from django.conf import settings | 11 from django.conf import settings |
12 from django.core.cache import cache | |
12 | 13 |
13 | 14 |
14 logger = logging.getLogger(__name__) | 15 logger = logging.getLogger(__name__) |
15 USER_AGENT = 'surfguitar101-gcalendar-v1' | 16 FLOW_CACHE_KEY = 'gcalendar-oauth-flow' |
16 REQ_TOKEN_SESSION_KEY = 'gcalendar oauth request token' | 17 FLOW_CACHE_TIMEOUT = 60 |
18 SCOPE = 'https://www.googleapis.com/auth/calendar' | |
17 | 19 |
18 | 20 |
19 def fetch_auth(request, scopes, callback_url): | 21 class OAuthError(Exception): |
22 """Base exception for errors raised by the oauth module""" | |
23 | |
24 | |
25 def check_credentials_status(): | |
26 """Performs a stat() on the credentials file and returns the last modified | |
27 time as a datetime object. If an error occurs, None is returned. | |
20 """ | 28 """ |
21 This function fetches a request token from Google and stores it in the | 29 try: |
22 session. It then returns the authorization URL as a string. | 30 status = os.stat(settings.GCAL_CREDENTIALS_PATH) |
31 return datetime.datetime.fromtimestamp(status.st_mtime) | |
32 except OSError: | |
33 return None | |
34 | |
35 | |
36 def get_auth_url(request, callback_url): | |
37 """ | |
38 This function creates an OAuth flow object and uses it to generate an | |
39 authorization URL which is returned to the caller. | |
23 | 40 |
24 request - the HttpRequest object for the user requesting the token. The | 41 request - the HttpRequest object for the user requesting the token. The |
25 token is stored in the session object attached to this request. | 42 token is stored in the session object attached to this request. |
26 | 43 |
27 scopes - a list of scope strings that the request token is for. See | |
28 http://code.google.com/apis/gdata/faq.html#AuthScopes | |
29 | |
30 callback_url - a string that is the URL that Google should redirect the user | 44 callback_url - a string that is the URL that Google should redirect the user |
31 to after the user has authorized our application access to their data. | 45 to after the user has authorized our application access to their data. |
32 | 46 |
33 This function only supports RSA-SHA1 authentication. Settings in the Django | |
34 settings module determine the consumer key and path to the RSA private key. | |
35 """ | 47 """ |
36 logger.info("fetch_auth started; callback url='%s'", callback_url) | 48 logger.info("get_auth_url started; callback url='%s'", callback_url) |
37 client = CalendarResourceClient(None, source=USER_AGENT) | |
38 | 49 |
39 with open(settings.GOOGLE_OAUTH_PRIVATE_KEY_PATH, 'r') as f: | 50 flow = OAuth2WebServerFlow(client_id=settings.GCAL_CLIENT_ID, |
40 rsa_key = f.read() | 51 client_secret=settings.GCAL_CLIENT_SECRET, |
41 logger.info("read RSA key; now getting request token") | 52 scope=SCOPE, |
53 redirect_uri=callback_url) | |
42 | 54 |
43 request_token = client.GetOAuthToken( | 55 auth_url = flow.step1_get_authorize_url() |
44 scopes, | 56 logger.info("auth url: '%s'", auth_url) |
45 callback_url, | |
46 settings.GOOGLE_OAUTH_CONSUMER_KEY, | |
47 rsa_private_key=rsa_key) | |
48 | 57 |
49 logger.info("received token") | 58 # Save the flow in the cache so we can use it when Google calls the |
50 request.session[REQ_TOKEN_SESSION_KEY] = request_token | 59 # callback. The expiration time also lets us check to make sure someone |
60 # isn't spoofing google if we are called at some random time. | |
61 # Note: using the session might seem like a more obvious choice, but flow | |
62 # objects are not JSON-serializable, and we don't want to use pickelable | |
63 # sessions for security reasons. | |
51 | 64 |
52 auth_url = request_token.generate_authorization_url() | 65 cache.set(FLOW_CACHE_KEY, flow, FLOW_CACHE_TIMEOUT) |
53 logger.info("generated auth url '%s'", str(auth_url)) | |
54 | 66 |
55 return str(auth_url) | 67 return auth_url |
56 | 68 |
57 | 69 |
58 def get_access_token(request): | 70 def auth_return(request): |
59 """ | 71 """ |
60 This function should be called after Google has sent the user back to us | 72 This function should be called after Google has sent the user back to us |
61 after the user authorized us. We retrieve the oauth token from the request | 73 after the user authorized us. We retrieve the authorization code from the |
62 URL and then upgrade it to an access token. We then return the access token. | 74 request query parameters and then exchange it for access tokens. These |
75 tokens are stored in our credentials file. | |
63 | 76 |
77 If an error is encountered, an OAuthError is raised. | |
64 """ | 78 """ |
65 logger.info("get_access_token called as '%s'", request.get_full_path()) | 79 logger.info("auth_return called as '%s'", request.get_full_path()) |
66 | 80 |
67 saved_token = request.session.get(REQ_TOKEN_SESSION_KEY) | 81 # Try to retrieve the flow object from the cache |
68 if saved_token is None: | 82 flow = cache.get(FLOW_CACHE_KEY) |
69 logger.error("saved request token not found in session!") | 83 if not flow: |
70 return None | 84 logger.warning("auth_return no flow in cache, bailing out") |
85 raise OAuthError("No flow found. Perhaps we timed out?") | |
71 | 86 |
72 logger.info("extracting token...") | 87 # Delete flow out of cache to close our operational window |
73 request_token = gdata.gauth.AuthorizeRequestToken(saved_token, | 88 cache.delete(FLOW_CACHE_KEY) |
74 request.build_absolute_uri()) | |
75 | 89 |
76 logger.info("upgrading to access token...") | 90 # Process the request |
91 error = request.GET.get('error') | |
92 if error: | |
93 logging.error("auth_return received error: %s", error) | |
94 raise OAuthError("Error authorizing request: %s" % error) | |
77 | 95 |
78 client = CalendarResourceClient(None, source=USER_AGENT) | 96 code = request.GET.get('code') |
79 access_token = client.GetAccessToken(request_token) | 97 if not code: |
98 logging.error("auth_return missing code") | |
99 raise OAuthError("No authorization code received") | |
80 | 100 |
81 logger.info("upgraded to access token...") | 101 logging.info("auth_return exchanging code for credentials") |
82 return access_token | 102 try: |
103 credentials = flow.step2_exchange(code) | |
104 except FlowExchangeError as ex: | |
105 logging.error("auth_return exchange failed: %s", ex) | |
106 raise OAuthError(str(ex)) | |
107 | |
108 logging.info("auth_return storing credentials") | |
109 storage = Storage(settings.GCAL_CREDENTIALS_PATH) | |
110 storage.put(credentials) | |
111 logging.info("auth_return completed successfully") | |
83 | 112 |
84 | 113 |
85 def serialize_token(token): | 114 def serialize_token(token): |
86 """ | 115 """ |
87 This function turns a token into a string and returns it. | 116 This function turns a token into a string and returns it. |