This takes two calls, one to the ClientLogin service of Google Accounts, and then a second to the login frontend of App Engine.
User credentials are provided to the first, which responds with a token. Passing that token to the _ah/login GAE endpoint then gives the cookie that can be used to make further authenticated requests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | """
Routines for programmatically authenticating with the Google Accounts system at
Google App-Engine.
This takes two calls, one to the ClientLogin service of Google Accounts,
and then a second to the login frontend of App Engine.
User credentials are provided to the first, which responds with a token.
Passing that token to the _ah/login GAE endpoint then gives the cookie that can
be used to make further authenticated requests.
Give the ACSID cookie to the client so it stays logged in with the GAE integrated users
system.
One last issue, after succesful authentication the current user's ID is still
missing; User(email).user_id() won't work. Here I think a HTTP redirect
should make the client re-request (using the cookie) and login, but the client
would need to support that. Alternatively the ID can be fetched within the
current request by a r/w round trip to the datastore, see:
http://stackoverflow.com/questions/816372/how-can-i-determine-a-user-id-based-on-an-email-address-in-app-engine
See also: http://markmail.org/thread/tgth5vmdqjacaxbx
"""
import logging, md5, urllib, urllib2
def do_auth(appname, user, password, dev=False, admin=False):
"This is taken from bits of appcfg, specifically: "
" google/appengine/tools/appengine_rpc.py "
"It returns the cookie send by the App Engine Login "
"front-end after authenticating with Google Accounts. "
if dev:
return do_auth_dev_appserver(user, admin)
# get the token
try:
auth_token = get_google_authtoken(appname, user, password)
except AuthError, e:
if e.reason == "BadAuthentication":
logging.error( "Invalid username or password." )
if e.reason == "CaptchaRequired":
logging.error(
"Please go to\n"
"https://www.google.com/accounts/DisplayUnlockCaptcha\n"
"and verify you are a human. Then try again.")
if e.reason == "NotVerified":
logging.error( "Account not verified.")
if e.reason == "TermsNotAgreed":
logging.error( "User has not agreed to TOS.")
if e.reason == "AccountDeleted":
logging.error( "The user account has been deleted.")
if e.reason == "AccountDisabled":
logging.error( "The user account has been disabled.")
if e.reason == "ServiceDisabled":
logging.error( "The user's access to the service has been "
"disabled.")
if e.reason == "ServiceUnavailable":
logging.error( "The service is not available; try again later.")
raise
# now get the cookie
cookie = get_gae_cookie(appname, auth_token)
assert cookie
return cookie
def do_auth_dev_appserver(email, admin):
"""Creates cookie payload data.
Args:
email, admin: Parameters to incorporate into the cookie.
Returns:
String containing the cookie payload.
"""
admin_string = 'False'
if admin:
admin_string = 'True'
if email:
user_id_digest = md5.new(email.lower()).digest()
user_id = '1' + ''.join(['%02d' % ord(x) for x in user_id_digest])[:20]
else:
user_id = ''
return 'dev_appserver_login="%s:%s:%s"; Path=/;' % (email, admin_string, user_id)
def get_gae_cookie(appname, auth_token):
"""
Send a token to the App Engine login, again stating the name of the
application to gain authentication for. Returned is a cookie that may be used
to authenticate HTTP traffic to the application at App Engine.
"""
continue_location = "http://localhost/"
args = {"continue": continue_location, "auth": auth_token}
host = "%s.appspot.com" % appname
url = "https://%s/_ah/login?%s" % (host,
urllib.urlencode(args))
opener = get_opener() # no redirect handler!
req = urllib2.Request(url)
try:
response = opener.open(req)
except urllib2.HTTPError, e:
response = e
if (response.code != 302 or
response.info()["location"] != continue_location):
raise urllib2.HTTPError(req.get_full_url(), response.code,
response.msg, response.headers, response.fp)
cookie = response.headers.get('set-cookie')
assert cookie and cookie.startswith('ACSID')
return cookie.replace('; HttpOnly', '')
def get_google_authtoken(appname, email_address, password):
"""
Make secure connection to Google Accounts and retrieve an authorisation
token for the stated appname.
The token can be send to the login front-end at appengine using
get_gae_cookie(), which will return a cookie to use for the user session.
"""
opener = get_opener()
# get an AuthToken from Google accounts
auth_uri = 'https://www.google.com/accounts/ClientLogin'
authreq_data = urllib.urlencode({ "Email": email_address,
"Passwd": password,
"service": "ah",
"source": appname,
"accountType": "HOSTED_OR_GOOGLE" })
req = urllib2.Request(auth_uri, data=authreq_data)
try:
response = opener.open(req)
response_body = response.read()
response_dict = dict(x.split("=")
for x in response_body.split("\n") if x)
return response_dict["Auth"]
except urllib2.HTTPError, e:
if e.code == 403:
body = e.read()
response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
raise AuthError(req.get_full_url(), e.code, e.msg,
e.headers, response_dict)
else:
raise
class AuthError(urllib2.HTTPError):
"""Raised to indicate there was an error authenticating."""
def __init__(self, url, code, msg, headers, args):
urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
self.args = args
self.reason = args["Error"]
def get_opener(cookiejar=None):
opener = urllib2.OpenerDirector()
opener.add_handler(urllib2.ProxyHandler())
opener.add_handler(urllib2.UnknownHandler())
opener.add_handler(urllib2.HTTPHandler())
opener.add_handler(urllib2.HTTPDefaultErrorHandler())
opener.add_handler(urllib2.HTTPErrorProcessor())
opener.add_handler(urllib2.HTTPSHandler())
if cookiejar:
opener.add_handler(urllib2.HTTPCookieProcessor(cookiejar))
return opener
|
Besides using the token to request Google user data, the cookie will also login a client at GAE integrated Google Accounts API too. Ie, after authenticating an (in my case gmail) account and setting the cookie, the next request GAE initializes the USER_EMAIL/USER_ID correctly into os.environ, from where users.User picks it up. See http://code.activestate.com/recipes/577235-gae-user-session-with-http-basic-authentication/