How do you access an authenticated Google App Engine service from a (non-web) python client?

dalelane picture dalelane · Sep 19, 2008 · Viewed 19.4k times · Source

I have a Google App Engine app - http://mylovelyapp.appspot.com/ It has a page - mylovelypage

For the moment, the page just does self.response.out.write('OK')

If I run the following Python at my computer:

import urllib2
f = urllib2.urlopen("http://mylovelyapp.appspot.com/mylovelypage")
s = f.read()
print s
f.close()

it prints "OK"

the problem is if I add login:required to this page in the app's yaml

then this prints out the HTML of the Google Accounts login page

I've tried "normal" authentication approaches. e.g.

passman = urllib2.HTTPPasswordMgrWithDefaultRealm()

auth_handler = urllib2.HTTPBasicAuthHandler()
auth_handler.add_password(None,
                          uri='http://mylovelyapp.appspot.com/mylovelypage',
                          user='[email protected]',
                          passwd='billybobspasswd')
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)

But it makes no difference - I still get the login page's HTML back.

I've tried Google's ClientLogin auth API, but I can't get it to work.

h = httplib2.Http()

auth_uri = 'https://www.google.com/accounts/ClientLogin'
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
myrequest = "Email=%s&Passwd=%s&service=ah&source=DALELANE-0.0" % ("[email protected]", "billybobspassword")
response, content = h.request(auth_uri, 'POST', body=myrequest, headers=headers)

if response['status'] == '200':
    authtok = re.search('Auth=(\S*)', content).group(1)

    headers = {}
    headers['Authorization'] = 'GoogleLogin auth=%s' % authtok.strip()
    headers['Content-Length'] = '0'

    response, content = h.request("http://mylovelyapp.appspot.com/mylovelypage", 
                                  'POST', 
                                  body="", 
                                  headers=headers)

    while response['status'] == "302":        
        response, content = h.request(response['location'], 'POST', body="", headers=headers) 

    print content

I do seem to be able to get some token correctly, but attempts to use it in the header when I call 'mylovelypage' still just return me the login page's HTML. :-(

Can anyone help, please?

Could I use the GData client library to do this sort of thing? From what I've read, I think it should be able to access App Engine apps, but I haven't been any more successful at getting the authentication working for App Engine stuff there either

Any pointers to samples, articles, or even just keywords I should be searching for to get me started, would be very much appreciated.

Thanks!

Answer

Nick Johnson picture Nick Johnson · Sep 19, 2008

appcfg.py, the tool that uploads data to App Engine has to do exactly this to authenticate itself with the App Engine server. The relevant functionality is abstracted into appengine_rpc.py. In a nutshell, the solution is:

  1. Use the Google ClientLogin API to obtain an authentication token. appengine_rpc.py does this in _GetAuthToken
  2. Send the auth token to a special URL on your App Engine app. That page then returns a cookie and a 302 redirect. Ignore the redirect and store the cookie. appcfg.py does this in _GetAuthCookie
  3. Use the returned cookie in all future requests.

You may also want to look at _Authenticate, to see how appcfg handles the various return codes from ClientLogin, and _GetOpener, to see how appcfg creates a urllib2 OpenerDirector that doesn't follow HTTP redirects. Or you could, in fact, just use the AbstractRpcServer and HttpRpcServer classes wholesale, since they do pretty much everything you need.