def generate_atom(gplus_id, page_id): """Generate an Atom-format feed for the given G+ id.""" # If no page id specified, use the special value 'me' which refers to the # stream for the owner of the OAuth2 token. request = requests.Request('GET', GPLUS_API_ACTIVITIES_ENDPOINT % (page_id or 'me'), params={ 'maxResults': 10, 'userIp': flask.request.remote_addr }) api_response = oauth2.authed_request_for_id(gplus_id, request) result = api_response.json() if page_id: request_url = full_url_for('page_atom', gplus_id=gplus_id, page_id=page_id) else: request_url = full_url_for('user_atom', gplus_id=gplus_id) params = { 'server_url': full_url_for('main'), 'feed_id': page_id or gplus_id, 'request_url': request_url, 'to_atom_date': dateutils.to_atom_format, } items = result.get('items') if not items: params['last_update'] = datetime.datetime.today() body = flask.render_template('atom/empty.xml', **params) else: last_update = max( dateutils.from_iso_format(item['updated']) for item in items) params['last_update'] = last_update params['items'] = process_feed_items(items) params['actor'] = params['items'][0]['actor'] body = flask.render_template('atom/feed.xml', **params) response = flask.make_response(body) response.headers['Content-Type'] = 'application/atom+xml; charset=utf-8' response.date = params['last_update'] return response
def main(): """Display the homepage.""" gplus_id = flask.request.cookies.get('gplus_id') if gplus_id: # If we lost rights, but someone still has the cookie, get rid of it. if not TokenIdMapping.lookup_refresh_token(gplus_id): return flask.redirect(flask.url_for('clear')) # Display a page indicating the user's feed URL, since they've authed. return flask.render_template('authed_main.html', datetime=datetime, gplus_id=gplus_id, feed_url=full_url_for('user_atom', gplus_id=gplus_id)) else: return flask.render_template('main.html', datetime=datetime)
def auth(): """Redirect the user to Google to obtain authorization.""" data = { # Basic OAuth2 parameters 'client_id': Config.get('oauth', 'client-id'), 'redirect_uri': full_url_for('oauth2'), 'scope': OAUTH2_SCOPE, 'response_type': 'code', # Settings necessary for daemon operation 'access_type': 'offline', 'approval_prompt': 'force', } return flask.redirect('%s/auth?%s' % (OAUTH2_BASE, urllib.urlencode(data)))
def generate_atom(gplus_id, page_id): """Generate an Atom-format feed for the given G+ id.""" # If no page id specified, use the special value 'me' which refers to the # stream for the owner of the OAuth2 token. request = requests.Request('GET', GPLUS_API_ACTIVITIES_ENDPOINT % (page_id or 'me'), params={'maxResults': 10, 'userIp': flask.request.remote_addr}) api_response = oauth2.authed_request_for_id(gplus_id, request) result = api_response.json() if page_id: request_url = full_url_for('page_atom', gplus_id=gplus_id, page_id=page_id) else: request_url = full_url_for('user_atom', gplus_id=gplus_id) params = { 'server_url': full_url_for('main'), 'feed_id': page_id or gplus_id, 'request_url': request_url, 'to_atom_date': dateutils.to_atom_format, } items = result.get('items') if not items: params['last_update'] = datetime.datetime.today() body = flask.render_template('atom/empty.xml', **params) else: last_update = max(dateutils.from_iso_format(item['updated']) for item in items) params['last_update'] = last_update params['items'] = process_feed_items(items) params['actor'] = params['items'][0]['actor'] body = flask.render_template('atom/feed.xml', **params) response = flask.make_response(body) response.headers['Content-Type'] = 'application/atom+xml; charset=utf-8' response.date = params['last_update'] return response
def oauth2(): """Google redirects the user back to this endpoint to continue the OAuth2 flow.""" # Check for errors from the OAuth2 process err = flask.request.args.get('error') if err == 'access_denied': return flask.redirect(flask.url_for('denied')) elif err is not None: app.logger.warning("OAuth2 callback received error: %s", err) # TODO: handle this better (flash message?) message = 'Whoops, something went wrong (error=%s). Please try again later.' return message % flask.escape(err), 500 # Okay, no errors, so we should have a valid authorization code. # Time to go get our server-side tokens for this user from Google. auth_code = flask.request.args['code'] if auth_code is None: return 'Authorization code is missing.', 400 # Bad Request data = { 'code': auth_code, 'client_id': Config.get('oauth', 'client-id'), 'client_secret': Config.get('oauth', 'client-secret'), 'redirect_uri': full_url_for('oauth2'), 'grant_type': 'authorization_code', } try: response = session.post(OAUTH2_BASE + '/token', data, timeout=GOOGLE_API_TIMEOUT) except requests.exceptions.Timeout: app.logger.error('OAuth2 token request timed out.') # TODO: handle this better (flash message?) message = 'Whoops, Google took too long to respond. Please try again later.' return message, 504 # Gateway Timeout if response.status_code != 200: app.logger.error( 'OAuth2 token request got HTTP response %s for code "%s".', response.status_code, auth_code) # TODO: handle this better (flash message?) message = ( 'Whoops, we failed to finish processing your authorization with Google.' ' Please try again later.') return message, 401 # Unauthorized try: result = response.json() except ValueError: app.logger.error( 'OAuth2 token request got non-JSON response for code "%s".', auth_code) # TODO: handle this better (flash message?) message = ( 'Whoops, we got an invalid response from Google for your authorization.' ' Please try again later.') return message, 502 # Bad Gateway # Sanity check: we always expect Bearer tokens. if result.get('token_type') != 'Bearer': app.logger.error( 'OAuth2 token request got unknown token type "%s" for code "%s".', result['token_type'], auth_code) # TODO: handle this better (flash message?) message = ( 'Whoops, we got an invalid response from Google for your authorization.' ' Please try again later.') return message, 502 # Bad Gateway # All non-error responses should have an access token. access_token = result['access_token'] refresh_token = result.get('refresh_token') # This is in seconds, but we convert it to an absolute timestamp so that we can # account for the potential delay it takes to look up the G+ id we should associate # the access tokens with. (Could be up to GOOGLE_API_TIMEOUT seconds later.) expiry = datetime.datetime.today() + datetime.timedelta( seconds=result['expires_in']) try: person = get_person_by_access_token(access_token) except UnavailableException as e: app.logger.error('Unable to finish OAuth2 flow: %r.' % e) message = ( 'Whoops, we got an invalid response from Google for your authorization.' ' Please try again later.') return message, 502 # Bad Gateway if refresh_token is not None: TokenIdMapping.update_refresh_token(person['id'], refresh_token) # Convert the absolute expiry timestamp back into a duration in seconds expires_in = int((expiry - datetime.datetime.today()).total_seconds()) Cache.set(ACCESS_TOKEN_CACHE_KEY_TEMPLATE % person['id'], access_token, time=expires_in) # Whew, all done! Set a cookie with the user's G+ id and send them back to the homepage. app.logger.info("Successfully authenticated G+ id %s.", person['id']) response = flask.make_response(flask.redirect(flask.url_for('main'))) response.set_cookie('gplus_id', person['id']) return response
def oauth2(): """Google redirects the user back to this endpoint to continue the OAuth2 flow.""" # Check for errors from the OAuth2 process err = flask.request.args.get('error') if err == 'access_denied': return flask.redirect(flask.url_for('denied')) elif err is not None: app.logger.warning("OAuth2 callback received error: %s", err) # TODO: handle this better (flash message?) message = 'Whoops, something went wrong (error=%s). Please try again later.' return message % flask.escape(err), 500 # Okay, no errors, so we should have a valid authorization code. # Time to go get our server-side tokens for this user from Google. auth_code = flask.request.args['code'] if auth_code is None: return 'Authorization code is missing.', 400 # Bad Request data = { 'code': auth_code, 'client_id': Config.get('oauth', 'client-id'), 'client_secret': Config.get('oauth', 'client-secret'), 'redirect_uri': full_url_for('oauth2'), 'grant_type': 'authorization_code', } try: response = session.post(OAUTH2_BASE + '/token', data, timeout=GOOGLE_API_TIMEOUT) except requests.exceptions.Timeout: app.logger.error('OAuth2 token request timed out.') # TODO: handle this better (flash message?) message = 'Whoops, Google took too long to respond. Please try again later.' return message, 504 # Gateway Timeout if response.status_code != 200: app.logger.error('OAuth2 token request got HTTP response %s for code "%s".', response.status_code, auth_code) # TODO: handle this better (flash message?) message = ('Whoops, we failed to finish processing your authorization with Google.' ' Please try again later.') return message, 401 # Unauthorized try: result = response.json() except ValueError: app.logger.error('OAuth2 token request got non-JSON response for code "%s".', auth_code) # TODO: handle this better (flash message?) message = ('Whoops, we got an invalid response from Google for your authorization.' ' Please try again later.') return message, 502 # Bad Gateway # Sanity check: we always expect Bearer tokens. if result.get('token_type') != 'Bearer': app.logger.error('OAuth2 token request got unknown token type "%s" for code "%s".', result['token_type'], auth_code) # TODO: handle this better (flash message?) message = ('Whoops, we got an invalid response from Google for your authorization.' ' Please try again later.') return message, 502 # Bad Gateway # All non-error responses should have an access token. access_token = result['access_token'] refresh_token = result.get('refresh_token') # This is in seconds, but we convert it to an absolute timestamp so that we can # account for the potential delay it takes to look up the G+ id we should associate # the access tokens with. (Could be up to GOOGLE_API_TIMEOUT seconds later.) expiry = datetime.datetime.today() + datetime.timedelta(seconds=result['expires_in']) try: person = get_person_by_access_token(access_token) except UnavailableException as e: app.logger.error('Unable to finish OAuth2 flow: %r.' % e) message = ('Whoops, we got an invalid response from Google for your authorization.' ' Please try again later.') return message, 502 # Bad Gateway if refresh_token is not None: TokenIdMapping.update_refresh_token(person['id'], refresh_token) # Convert the absolute expiry timestamp back into a duration in seconds expires_in = int((expiry - datetime.datetime.today()).total_seconds()) Cache.set(ACCESS_TOKEN_CACHE_KEY_TEMPLATE % person['id'], access_token, time=expires_in) # Whew, all done! Set a cookie with the user's G+ id and send them back to the homepage. app.logger.info("Successfully authenticated G+ id %s.", person['id']) response = flask.make_response(flask.redirect(flask.url_for('main'))) response.set_cookie('gplus_id', person['id']) return response