AuthSub with Text_db in google app engine - python

I am trying to read a spreadsheet from app engine using text_db and authsub.
I read http://code.google.com/appengine/articles/gdata.html and got it to work. Then I read http://code.google.com/p/gdata-python-client/wiki/AuthSubWithTextDB and I tried to merge the two in the file below (step4.py) but when I run it locally I get:
Traceback (most recent call last):
File "/home/jmvidal/share/progs/googleapps/google_appengine/google/appengine/ext/webapp/__init__.py", line 498, in __call__
handler.get(*groups)
File "/home/jmvidal/share/progs/googleapps/google_appengine/glassboard/step4.py", line 56, in get
session_token = client._GetDocsClient().UpgradeToSessionToken(auth_token) #If I don't pass this argument I get a NonAuthSubToken
File "/home/jmvidal/share/progs/googleapps/google_appengine/glassboard/gdata/service.py", line 866, in UpgradeToSessionToken
self.SetAuthSubToken(self.upgrade_to_session_token(token))
File "/home/jmvidal/share/progs/googleapps/google_appengine/glassboard/gdata/service.py", line 885, in upgrade_to_session_token
headers={'Content-Type':'application/x-www-form-urlencoded'})
File "/home/jmvidal/share/progs/googleapps/google_appengine/glassboard/gdata/auth.py", line 678, in perform_request
return http_client.request(operation, url, data=data, headers=headers)
File "/home/jmvidal/share/progs/googleapps/google_appengine/glassboard/atom/http.py", line 163, in request
return connection.getresponse()
File "/home/jmvidal/share/progs/googleapps/google_appengine/google/appengine/dist/httplib.py", line 200, in getresponse
self._allow_truncated, self._follow_redirects)
File "/home/jmvidal/share/progs/googleapps/google_appengine/google/appengine/api/urlfetch.py", line 267, in fetch
raise DownloadError(str(e))
DownloadError: ApplicationError: 2 nonnumeric port: ''
Can anyone shed some light on this? Specifically, why is it that the original (step3.py from the first link) works but my call here to UpgradeToSessionToken fails?
# step4.py
#
# Trying to read spreadsheets from app engine using text_db and authsub.
#
# Merge of this code
# http://code.google.com/p/gdata-python-client/wiki/AuthSubWithTextDB
# with this one
# http://code.google.com/appengine/articles/gdata.html (step 3)
import wsgiref.handlers
import cgi
from google.appengine.ext import webapp
from google.appengine.api import users
import atom.url
import gdata.service
import gdata.alt.appengine
import gdata.spreadsheet.text_db
import settings
class Fetcher(webapp.RequestHandler):
def get(self):
# Write our pages title
self.response.out.write("""<html><head><title>
Google Data Feed Fetcher: read Google Data API Atom feeds</title>""")
self.response.out.write('</head><body>')
next_url = atom.url.Url('http', settings.HOST_NAME, path='/step4')
# Allow the user to sign in or sign out
if users.get_current_user():
self.response.out.write('Sign Out<br>' % (
users.create_logout_url(str(next_url))))
else:
self.response.out.write('Sign In<br>' % (
users.create_login_url(str(next_url))))
# Initialize a client to talk to Google Data API services.
# client = gdata.service.GDataService()
# auth_url = client.GenerateAuthSubURL(
# next_url,
# ('http://docs.google.com/feeds/',), secure=False, session=True)
client = gdata.spreadsheet.text_db.DatabaseClient()
auth_url = client._GetDocsClient().GenerateAuthSubURL(
next_url,
('http://spreadsheets.google.com/feeds/','http://docs.google.com/feeds/documents/'), secure=False, session=True)
gdata.alt.appengine.run_on_appengine(client)
feed_url = self.request.get('feed_url')
session_token = None
# Find the AuthSub token and upgrade it to a session token.
auth_token = gdata.auth.extract_auth_sub_token_from_url(self.request.uri)
if auth_token:
# Upgrade the single-use AuthSub token to a multi-use session token.
client._GetDocsClient().SetAuthSubToken(auth_token)
session_token = client._GetDocsClient().UpgradeToSessionToken(auth_token) #If I don't pass this argument I get a NonAuthSubToken
client._GetSpreadsheetsClient().SetAuthSubToken(client._GetDocsClient().GetAuthSubToken())
# session_token = client.upgrade_to_session_token(auth_token)
if session_token and users.get_current_user():
# If there is a current user, store the token in the datastore and
# associate it with the current user. Since we told the client to
# run_on_appengine, the add_token call will automatically store the
# session token if there is a current_user.
client.token_store.add_token(session_token)
elif session_token:
# Since there is no current user, we will put the session token
# in a property of the client. We will not store the token in the
# datastore, since we wouldn't know which user it belongs to.
# Since a new client object is created with each get call, we don't
# need to worry about the anonymous token being used by other users.
client.current_token = session_token
self.response.out.write('<div id="main">')
self.fetch_feed(client, feed_url)
self.response.out.write('</div>')
self.response.out.write(
'<div id="sidebar"><div id="scopes"><h4>Request a token</h4><ul>')
self.response.out.write('<li>Google Documents</li>' % (auth_url))
self.response.out.write('</ul></div><br/><div id="tokens">')
def fetch_feed(self, client, feed_url):
# Attempt to fetch the feed.
if not feed_url:
self.response.out.write(
'No feed_url was specified for the app to fetch.<br/>')
example_url = atom.url.Url('http', settings.HOST_NAME, path='/step4',
params={'feed_url':
'http://docs.google.com/feeds/documents/private/full'}
).to_string()
self.response.out.write('Here\'s an example query which will show the'
' XML for the feed listing your Google Documents <a '
'href="%s">%s</a>' % (example_url, example_url))
return
try:
response = client.Get(feed_url, converter=str)
self.response.out.write(cgi.escape(response))
except gdata.service.RequestError, request_error:
# If fetching fails, then tell the user that they need to login to
# authorize this app by logging in at the following URL.
if request_error[0]['status'] == 401:
# Get the URL of the current page so that our AuthSub request will
# send the user back to here.
next = atom.url.Url('http', settings.HOST_NAME, path='/step4',
params={'feed_url': feed_url})
# If there is a current user, we can request a session token, otherwise
# we should ask for a single use token.
auth_sub_url = client.GenerateAuthSubURL(next, feed_url,
secure=False, session=True)
self.response.out.write('<a href="%s">' % (auth_sub_url))
self.response.out.write(
'Click here to authorize this application to view the feed</a>')
else:
self.response.out.write(
'Something went wrong, here is the error object: %s ' % (
str(request_error[0])))
def main():
application = webapp.WSGIApplication([('/.*', Fetcher),], debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == '__main__':
main()

As always, I figure out the answer only after giving up and asking for help.
we need to add two more calls to run_on_appengine (to register the two clients that the text_db client has):
gdata.alt.appengine.run_on_appengine(client)
gdata.alt.appengine.run_on_appengine(client._GetDocsClient())
gdata.alt.appengine.run_on_appengine(client._GetSpreadsheetsClient()) here
I would have expected the first call to run_on_appengine to result in the two other calls, but I guess not.
Oh, and change the auth_url line to:
auth_url = client._GetDocsClient().GenerateAuthSubURL(
next_url,scope='http://spreadsheets.google.com/feeds/ http://docs.google.com/feeds/documents/', secure=False, session=True)
Passing the scope urls in a list caused a "token does not have the correct scope" error.

Related

Capture initial URL and Return to it after Azure SSO Authentication using MSAL for Python Dash App

I have put together my first dash app with the help of lots of online samples.
The app parses the URL for callbacks. The URL format is HTTP://www.app.root.com/ID/UID
If the user is already authenticated everything works fine, but if the user is not already authenticated the user is redirected for authentication via Azure SSO and then returned to the root URL.
How can I capture the initial URL entered and redirect to it after authentication?
#app.server.route(PATH_LOGIN)
def login():
"""
Start a login redirection
"""
# define a random id for a new session
session["state"] = str(uuid.uuid4())
# Technically we could use empty list [] as scopes to do just sign in,
# here we choose to also collect end user consent upfront
try:
session["flow"] = _build_auth_code_flow(scopes=SCOPE, state=session['state'])
# pylint: disable=broad-except
except Exception as ex:
msg = str(ex)
print(f"{func_name()}: {msg}")
session["flow"] = {}
print(f"{func_name()}: session['flow']: {session['flow']}")
return redirect(session["flow"].get("auth_uri"))
# Its absolute URL must match one of the app's redirect_uris set in the Azure Application Registry
#app.server.route(PATH_AUTHORIZATION)
def authorized() -> "Response":
"""
Process the response from the authorization mechanism (AAD: Azure Active Directory)
Returns:
The URL to use next, either to access the app or to retry the login.
"""
# useful for initial debugging
print(f"{func_name()}: request.args:\n {request.args}")
if request.args.get('state') != session.get("state"):
# return redirect(url_for("index")) # No-OP. Goes back to Index page
print(f"{func_name()}: Redirecting to Index: {request.args.get('state')} different than {session.get('state')}")
return redirect(url_for("login")) # Go back to Index page
if "error" in request.args:
# process Authentication/Authorization failure
print(f"{func_name()}: request.args: {request.args}")
session['auth_error'] = _tuples_to_dict(request.args)
print(f"{func_name()}: Authentication/Authorization failure: {session['auth_error']}")
return redirect(url_root() + PATH_ROOT)
if request.args.get('code'):
# process received authorization code
cache = _load_cache()
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}),
request.args
)
print(f"{func_name()}: user result: {result}")
if "error" in result:
session['auth_error'] = result
print(func_name(), result)
return redirect(url_root() + PATH_ROOT)
session["user"] = result.get("id_token_claims")
_save_cache(cache)
print(f"{func_name()}: Successful authorization")
return redirect(url_root() + PATH_ROOT)
I have tried adding this:
#app.server.route('/<subpath>')
def show_subpath(subpath):
print(subpath)
return subpath
And I think I need to use it as the "next_url" after "return redirect(url_for("login"))" and/or append it to the end of the return redirect(url_root() + PATH_ROOT), but it's not clear to me how to use the subpath value.
Thank you for any help!

How to send and read mail using Microsoft graph api (python-o365) in Python Django using web app based authentication?

I am new to using Microsoft Graph API for office 365 integration. I have gone through the official documentation at https://github.com/O365/python-o365 and I am able to implement mail send and mail read using console stuff.
Code is below:
from O365 import Account
credentials = ('5ed870b6-efee-41bb-83cf-1234567890', '6a~4o-F_2~z__n97iaNAG7f7r_1234567890')
account = Account(credentials)
if account.authenticate(scopes=['basic', 'message_all']):
print('Authenticated!')
Here it asks for url to copy and paste done, then I can
def readMail(convId, account):
mailbox = account.mailbox()
test_mailbox = mailbox.get_folder(folder_name="Inbox")
query = mailbox.new_query().on_attribute('conversation_id').equals(convId)
listOfMails = []
for message in test_mailbox.get_messages(limit=100, query=query, batch=20):
mail_time = message.received
# print(mail_time)
mail_ID = message.internet_message_id
mail_sender = message.sender
mail_cc = message.cc
mail_subject = message.subject
mail_content = message.get_body_text()
mail_dict = {'mail_time': mail_time, 'mail_ID': mail_ID, 'mail_sender': mail_sender, 'mail_cc': mail_cc,
'mail_subject': mail_subject, 'mail_content': mail_content}
listOfMails.append(mail_dict)
lastMail = listOfMails[len(listOfMails) - 1]
# print(len(listOfMails))
return lastMail
convId = 'AAQkAGFiYTcwOWM1LTgwNWQtNDk0Ny1hY2I0LWRjNTM2NTVjMmFhMgAQAO_HWbiF8xtLtpSPxMKOGtA='
lastMail = readMail(convId, account)
print(lastMail)
def sendMail(lastMail, account):
mail_id = lastMail.get('mail_ID')
mailbox = account.mailbox()
test_mailbox = mailbox.get_folder(folder_name="Inbox")
query = mailbox.new_query().on_attribute('internet_message_id').contains(mail_id) # see Query object in Utils
for message in test_mailbox.get_messages(1, query=query):
# print("dsdsdf")
print(message.get_body_text())
message.mark_as_read()
reply_msg = message.reply()
reply_msg.body = 'Sorry Its just a Test Mail !'
reply_msg.attachments.add('C:/Users/geetanjali.sharma/Pictures/Screenshots/Sabbatical_Fail/Screenshot (16).png')
reply_msg.send()
sendMail(lastMail, account)
But when I try to do the same thing with Web app based authentication interface.
My code:
from O365 import Account,FileSystemTokenBackend
import os
credentials = ('7c00f426-2ac5-486f-a137-1234567890', 'cOtN3a4_e.dv0WNF0.qN.xx5wgF42Tu.10.')
token_backend = FileSystemTokenBackend(token_path=os.getcwd()+'/token_folder/', token_filename='my_token.txt')
account = Account(credentials, auth_flow_type='credentials', tenant_id='3d4f5678b727a530-a0d5-4fb8-bd40-d8f9763e97db',token_backend=token_backend)
if account.authenticate():
print('Authenticated!')
m = account.new_message()
m.to.add('someone#someone.com')
m.subject = 'Testing!'
m.body = "George Best quote: I've stopped drinking, but only while I'm asleep."
m.send()
i get error
Authenticated!
Client Error: 400 Client Error: Bad Request for url: https://graph.microsoft.com/v1.0/sendMail | Error Message: Resource not found for the segment 'sendMail'.
Traceback (most recent call last):
File "/home/ntl/Documents/projects/dataIngestionApp/dataingestionBackend/dataIngestionML/send_mail.py", line 13, in <module>
m.send()
File "/home/ntl/anaconda3/envs/dataai/lib/python3.7/site-packages/O365/message.py", line 694, in send
response = self.con.post(url, data=data)
File "/home/ntl/anaconda3/envs/dataai/lib/python3.7/site-packages/O365/connection.py", line 811, in post
return self.oauth_request(url, 'post', data=data, **kwargs)
File "/home/ntl/anaconda3/envs/dataai/lib/python3.7/site-packages/O365/connection.py", line 789, in oauth_request
return self._internal_request(self.session, url, method, **kwargs)
File "/home/ntl/anaconda3/envs/dataai/lib/python3.7/site-packages/O365/connection.py", line 751, in _internal_request
raise HTTPError('{} | Error Message: {}'.format(e.args[0], error_message), response=response) from None
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://graph.microsoft.com/v1.0/sendMail | Error Message: Resource not found for the segment 'sendMail'.
Process finished with exit code 1
Also adding my apps permission in azure
You need to set the resource for the message.
account = Account(credentials, auth_flow_type='credentials', tenant_id=tenant_id)
m = account.mailbox(resource='{the email address of sender}')

Basic Google API Oauth2 Authentication Setup Fails To Give Objects?

I have coded an authentication and I get an access token, but when I use it to grab an object I just end up getting 'NoneType' object is not callable.
Exception Location: googleads\adwords.py in GetService, line 365
Exception Type: Type Error
Exception Value: Internal Server Error: /oauth2callback
I get the same result whether calling for Customer or CampaignService. I don't understand what I am doing wrong.
I am following the code in Googleads.
def getAdwordsFlow():
FLOW.redirect_uri = 'http://localhost/oauth2callback'
# Generate URL for request to Google's OAuth 2.0 server.
authorization_url, state = FLOW.authorization_url(
access_type='offline',
include_granted_scopes='true')
return authorization_url
def getAdwordsTokens(request):
auth_code = request.GET.get('code')
FLOW.fetch_token(code=auth_code)
credentials = FLOW.credentials
oauth2_client = oauth2.GoogleAccessTokenClient(
FLOW.credentials.token, FLOW.credentials.expiry)
adwords_client = adwords.AdWordsClient(
"DEVELOPER-TOKEN", oauth2_client, "USER-AGENT", "CLIENT-CUSTOMER-ID")
customer = adwords_client.GetService('CustomerService').getCustomers()[0]
print('You are logged in as customer: %s' % customer['customerId'])
return HttpResponseRedirect("/")
url.py
urlpatterns = [
re_path(r'^oauth2callback', queries.getAdwordsTokens, name='auth_calledback'),] #How
view.py
def index(request):
return redirect(getAdwordsFlow())
Terminal output:
"GET /oauth2callback?state=XXXXXXXXX&code=4/XXXXXXXXXXX&scope=https://www.googleapis.com/auth/adwords+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email HTTP/1.1" 500 80213
Why is it 500?
I notice my access token has a different value when I call for it. So I am assuming it's working.
According to your question, your service call is -
GET /oauth2callback?state=XXXXXXXXX&code=4/XXXXXXXXXXX&scope=https://www.googleapis.com/auth/adwords+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email HTTP/1.1
This call is neither get access token call nor having access_token in the request and as per your reference code it will generate access_token from refresh_token.
def main(access_token, token_expiry, client_customer_id, developer_token,
user_agent):
oauth2_client = oauth2.GoogleAccessTokenClient(access_token, token_expiry)
adwords_client = adwords.AdWordsClient(
developer_token, oauth2_client, user_agent,
client_customer_id=client_customer_id)
customer = adwords_client.GetService('CustomerService').getCustomers()[0]
print 'You are logged in as customer: %s' % customer['customerId']
if __name__ == '__main__':
args = parser.parse_args()
# Retrieve a new access token for use in this example. In a production
# application, you may use a credential store to share access tokens for a
# given user across applications.
oauth2credentials = client.OAuth2Credentials(
None, args.client_id, args.client_secret, args.refresh_token,
datetime.datetime(1980, 1, 1, 12), GOOGLE_OAUTH2_ENDPOINT,
USER_AGENT)
oauth2credentials.refresh(httplib2.Http())
main(oauth2credentials.access_token, oauth2credentials.token_expiry,
args.client_customer_id, args.developer_token, USER_AGENT)
So to use your code, first generate refresh_token using this code and then use it in your given code.

Run particular Django script in background

I am developing a Gmail extracting app and using Gmail API to fetch mail from server. the problem lies in the fact that fetch time for mails is too large even though I used threading in back end framework. now I am going to implement one feature which will suggest user opting for bulk download that "once your download is ready, we will mail you" but for that i want to run download.py mentioned below in app tree in background and once the fetch is over it will get terminated.
And in the very bottom of the code i want to mail user that their download is ready but its not working though i have defined the mail server in settings.py .
download.py
import httplib2, base64
from stripogram import html2text
from oauth2client.django_orm import Storage
from apiclient.discovery import build
from oauth2client import client
from django.contrib.auth.models import User
from .models import CredentialsModel
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from gextracto import models
from gextracto.models import UserData
from django.core.mail import EmailMessage
from django.core import mail
connection = mail.get_connection()
class ListMails(APIView):
"""
Gets a list of a specified number mail ids for a particular label
Extracts the email in the form of plain/text
The API returns all the extracted mails
"""
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def extract_headers(self, message):
"""
Extract the headers for a single mail and returns it
{To, From, Subject}
"""
needed_fields = ('From', 'To', 'Subject')
return {i['name']:i['value'] for i in message['payload']['headers'] if i['name'] in needed_fields}
def get_message_body(self, message):
"""
Get the body of an email
Recursively look for the body for different mimetypes
Returns the body as text/plain
"""
if 'payload' in message:
return self.get_message_body(message['payload'])
elif 'parts' in message:
return self.get_message_body(message['parts'][0])
else:
data = base64.urlsafe_b64decode(message['body']['data'].encode('ASCII'))
markdown_data = html2text(data)#.decode('utf-8', "replace")
data = data.replace("\n", "<br/>")
# return {markdown, html}
return {'markdown':unicode( markdown_data,"ISO-8859-1"), 'html':unicode(data,"ISO-8859-1")} if markdown_data else {'html':unicode(data,"ISO-8859-1")}
def message_content_html(self, userId, message_id, service):
"""
Make queries to get the content for a mail given its message id
Returns all the content
"""
content = {'id':message_id}
# try
message = service.users().messages().get(userId=userId, id=message_id).execute()
mimetype = message['payload']['mimeType']
if mimetype == 'text/html':
return {}
#
else:
body = self.get_message_body(message)
if body == "":
body = "<empty message>"
headers = self.extract_headers(message)
content['body'] = body
content.update(headers)
return content
def collect_mails(self, user, messages, service):
"""
Collect the content for all the mails currently downloaded
"""
all_messages = []
try:
for message in messages:
content = self.message_content_html(user.username, message['id'], service)
if content:
all_messages.append(content)
return all_messages
# return empty list if no messages were downloaded
except KeyError:
return []
def get(self, request, format=None):
"""
Handles the GET request to get all the mails for a label
Paginages through the GAPI content if required
API returns all the messages
{To, From, Subject, body}
"""
user = request.user
storage = Storage(CredentialsModel, 'id', user, 'credential')
credentials = storage.get()
http_auth = credentials.authorize(httplib2.Http())
service = build('gmail', 'v1', http=http_auth)
user_Id = user.username
label_id = request.GET['label']
# try
# call Google API with a request to get a list of all the labels
response = service.users().messages().list(userId=user_Id, labelIds=label_id, maxResults=100).execute()
all_messages = self.collect_mails(user, response['messages'], service)
if not all_messages:
return Response([])
else:
if 'nextPageToken' in response:
page_token_flag = True
# request more more mails if the download limit has not yet been satisfied
while(page_token_flag):
response = service.users().messages().list(userId=user_Id, pageToken=response['nextPageToken'], maxResults=100).execute()
all_messages.append(self.collect_mails(user, response['messages'], service))
print(all_messages)
#for x in range(0,len(all_messages)):
#b=all_messages[10]
#instance= UserData(user_id=user ,label=label_id, sender = b['From'] , subject=b['Subject'] , body=b['body'])
#instance.save()
page_token_flag = 'nextPageToken' in response
##
for x in range(0,len(all_messages)):
b=all_messages[10]
instance= UserData(user_id=user ,label=label_id, sender = b['From'] , subject=b['Subject'] , body=b['body'])
instance.save()
print ("Hi i am here!!!")
email = EmailMessage('Your Download Ready!', 'http://127.0.0.1:8000/admin/gextracto/userdata/', to=[user], connection=connection)
email.send()
connection.close()
return Response(all_messages)
Please tell me the way to run it in background. if need any other info please do ask. Thanks
Don't know the exact requirements but I'll think about Celery to run background tasks. This approach allows to manage all post-script activities in native Django manner.
Also you can think about running the Django script using cron (as manage.py command) - but it can lead to some limitations.
What about sending emails failure - believe, you don't need to close connection after sending email. Usually I use send_mail()/send_mass_mail() functions - please, check their code to get an idea.

Appengie Oauth: Spreadsheet api client.get_spreadsheets() returns error

trying to list the google spreadsheet using oauth & GData in Google Appengine. But i'm getting following error. Anything wrong in my code?
Please advise.
File "/home/RKS/Appengine/test/main.py", line 99, in get
feed = client.get_spreadsheets()
File "/home/RKS/Appengine/test/gdata/spreadsheets/client.py", line 78, in get_spreadsheets
desired_class=desired_class, **kwargs)
File "/home/RKS/Appengine/test/gdata/client.py", line 640, in get_feed
**kwargs)
File "/home/RKS/Appengine/test/gdata/client.py", line 278, in request
version=get_xml_version(self.api_version))
File "/home/RKS/Appengine/test/atom/core.py", line 520, in parse
tree = ElementTree.fromstring(xml_string)
File "<string>", line 125, in XML
ParseError: no element found: line 1, column 0
Code:
#!/usr/bin/env python
import webapp2
import logging
from google.appengine.api import users
import gdata.auth
import gdata.gauth
import gdata.client
import gdata.service
import gdata.spreadsheets
import gdata.spreadsheets.client
import gdata.spreadsheets.data
SETTINGS = {
'APP_NAME': 'hidden', # intentionally hidden
'CONSUMER_KEY': 'hidden',
'CONSUMER_SECRET': 'hidden',
'SCOPES': ['https://spreadsheets.google.com/feeds'],
'CALLBACK_URL': '',
'SIG_METHOD': gdata.auth.OAuthSignatureMethod.HMAC_SHA1
}
class FetchToken(webapp2.RequestHandler):
def get(self):
current_uid = users.get_current_user().user_id()
if isinstance(gdata.gauth.AeLoad(current_uid), gdata.gauth.OAuthHmacToken):
# the user has gone through the process before
self.redirect('/')
else:
# the user has not gone through this process, or the authorization has expired or been revoked.
SETTINGS['CALLBACK_URL'] = 'http://%s/HandleOAuthCallback' % self.request.host
client = gdata.client.GDClient()
request_token = client.GetOAuthToken(
SETTINGS['SCOPES'],
SETTINGS['CALLBACK_URL'],
SETTINGS['CONSUMER_KEY'],
consumer_secret=SETTINGS['CONSUMER_SECRET'])
gdata.gauth.AeSave(request_token, current_uid)
self.redirect(str(request_token.generate_authorization_url()))
class HandleOAuthCallback(webapp2.RequestHandler):
def get(self):
current_uid = users.get_current_user().user_id()
client = gdata.client.GDClient()
saved_request_token = gdata.gauth.AeLoad(current_uid)
request_token = gdata.gauth.AuthorizeRequestToken(saved_request_token, self.request.uri)
access_token = client.GetAccessToken(request_token)
gdata.gauth.AeSave(access_token, current_uid)
self.redirect('/')
class GetDocsList(webapp2.RequestHandler):
def get(self):
if not users.get_current_user():
self.redirect(users.create_login_url('/'))
else:
current_uid = users.get_current_user().user_id()
if isinstance(gdata.gauth.AeLoad(current_uid), gdata.gauth.OAuthHmacToken):
# the user has gone through the process before
access_token = gdata.gauth.AeLoad(current_uid)
client = gdata.service.GDataService()
oauth_input_params = gdata.auth.OAuthInputParams(
gdata.auth.OAuthSignatureMethod.HMAC_SHA1,
SETTINGS['CONSUMER_KEY'],
SETTINGS['CONSUMER_SECRET']) # consumer_secret=
oauth_token = gdata.auth.OAuthToken(
key=access_token.token,
secret=access_token.token_secret,
scopes=SETTINGS['SCOPES'],
oauth_input_params=oauth_input_params)
client.SetOAuthToken(oauth_token)
client = gdata.spreadsheets.client.SpreadsheetsClient()
feed = client.get_spreadsheets()
else:
self.redirect('/FetchToken')
app = webapp2.WSGIApplication([('/HandleOAuthCallback', HandleOAuthCallback), ('/', GetDocsList), ('/FetchToken', FetchToken)], debug=False)
I recommend switching to OAuth2, and most of my answer will explain how to solve your problem by doing that. However, before trying to switch to OAuth2 I suggest you try this one line change, which may be a quick solution to your problem. The first thing I would try is adding a header to your client as follows.
client.additional_headers = {
'Authorization': 'Bearer %s' % access_token,
}
I have an application using the GData spreadsheets API with OAuth2 and adding that header was necessary for it to work. If I took that header out, I would get an error like the one you described.
Now, on to the answer:
Using OAuth2 in App Engine to do what you're trying to do is even simpler than what you've already done. Here's how it is done.
First create an OAuth2 decorator. (I describe where to create your client ID and client secret at the end of the answer.)
from oauth2client.appengine import OAuth2Decorator
GlobalOAuth2Decorator = OAuth2Decorator(
client_id=OAUTH2_CLIENT_ID,
client_secret=OAUTH2_CLIENT_SECRET,
scope='https://spreadsheets.google.com/feeds',
)
Then use this decorator when you create your request handler.
class SpreadsheetHandler(webapp2.RequestHandler):
#GlobalOAuth2Decorator.oauth_required
def get(self):
client = gdata.spreadsheet.service.SpreadsheetsService()
client.additional_headers = {
'Authorization': 'Bearer %s' % GlobalOAuth2Decorator.credentials.access_token,
}
Notice that I'm using SpreadsheetsService instead of SpreadsheetsClient. This has been working for me, though I haven't given SpreadsheetsClient a try (the client might actually be easier to use).
Once you've created this client object you can use it to read and write spreadsheets as you like. For example sheets = client.GetSpreadsheetsFeeds() will give you a list of spreadsheets that you can access.
Lastly, make sure to include the OAuth handler in your list of handlers when you create the application:
app = webapp2.WSGIApplication([..., (GlobalOAuth2Decorator.callback_path, GlobalOAuth2Decorator.callback_handler())]
In order for OAuth2 to work, you have to go to your developer console at https://console.developers.google.com and 1) select your app, 2) from the Credentials menu create a new Client ID (this will generate a client ID and secret for you), and 3) from the Consent Screen menu give your application an email address and name.

Categories