1 from httplib import HTTPConnection, HTTPSConnection
2 import re
3
4 class GoogleLoginAuthError(Exception): pass
5 class GoogleLoginAuthUnknownError(Exception): pass
6
7 # In regex below:
8 # [^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+ matches a "token" as defined by HTTP
9 # "(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?" matches a "quoted-string" as defined by HTTP, when LWS have already been replaced by a single space
10 # Actually, as an auth-param value can be either a token or a quoted-string, they are combined in a single pattern which matches both:
11 # \"?((?<=\")(?:[^\0-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?<!\")[^\0-\x08\x0A-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+(?!\"))\"?
12 WWW_AUTH = re.compile(r"^(?:\s*(?:,\s*)?([^ \t\r\n=]+)\s*=\s*\"?((?<=\")(?:[^\\\"]|\\.)*?(?=\")|(?<!\")[^ \t\r\n,]+(?!\"))\"?)(.*)$")
13 UNQUOTE_PAIRS = re.compile(r'\\(.)')
14 def _parse_www_authenticate(authenticate):
15 """Returns a dictionary of dictionaries, one dict
16 per auth-scheme. The dictionary for each auth-scheme
17 contains all the auth-params.
18 """
19 retval = {}
20 authenticate = authenticate.strip()
21 while authenticate:
22 # Break off the scheme at the beginning of the line
23 (auth_scheme, the_rest) = authenticate.split(" ", 1)
24 # Now loop over all the key value pairs that come after the scheme,
25 # being careful not to roll into the next scheme
26 match = WWW_AUTH.search(the_rest)
27 auth_params = {}
28 while match:
29 if match and len(match.groups()) == 3:
30 (key, value, the_rest) = match.groups()
31 auth_params[key.lower()] = UNQUOTE_PAIRS.sub(r'\1', value) # '\\'.join([x.replace('\\', '') for x in value.split('\\\\')])
32 match = WWW_AUTH.search(the_rest)
33 retval[auth_scheme.lower()] = auth_params
34 authenticate = the_rest.strip()
35 return retval
36
37 GOOGLE_ERROR_MESSAGES = {
38 "BadAuthentication": "The login request used a username or password that is not recognized.",
39 "NotVerified": "The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.",
40 "TermsNotAgreed": "The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.",
41 "CaptchaRequired": "Please visit https://www.google.com/accounts/DisplayUnlockCaptcha to enable access.",
42 "Unknown": "The error is unknown or unspecified; the request contained invalid input or was malformed.",
43 "AccountDeleted": "The user account has been deleted.",
44 "AccountDisabled": "The user account has been disabled.",
45 "ServiceDisabled": "The user's access to the specified service has been disabled. (The user account may still be valid.)",
46 "ServiceUnavailable": "The service is not available; try again later.",
47 }
48
49 def googlelogin(name, password, useragent, challenge):
50 from urllib import urlencode
51 service = challenge['googlelogin']['service']
52 auth = dict(Email=name, Passwd=password, service=service, source=useragent)
53 conn = HTTPSConnection('www.google.com')
54 conn.request('POST', '/accounts/ClientLogin', body=urlencode(auth), headers={'content-type': 'application/x-www-form-urlencoded'})
55 r = conn.getresponse()
56 content = r.read()
57 lines = content.split('\n')
58 d = dict([tuple(line.split("=", 1)) for line in lines if line])
59 if r.status == 403:
60 if 'Error' in d:
61 errorname = d['Error']
62 if errorname in GOOGLE_ERROR_MESSAGES:
63 raise GoogleLoginAuthError(GOOGLE_ERROR_MESSAGES[errorname])
64 else:
65 raise GoogleLoginAuthUnknownError(errorname)
66 auth = ""
67 else:
68 auth = d['Auth']
69 return auth
70
71 # Try an operation
72 headers = {}
73 conn = HTTPConnection('www.blogger.com')
74 conn.request("GET", "/feeds/default/blogs", headers=headers)
75 r = conn.getresponse()
76 print r.status
77 content = r.read()
78 # If we get a 401 then respond to the challenge
79 if r.status == 401:
80 name, password = open("/home/jcgregorio/gmail", "r").read().split()
81 useragent = "My-App-01"
82 headers = {}
83 challenge = _parse_www_authenticate(r.getheader('www-authenticate'))
84 if 'googlelogin' in challenge:
85 auth = googlelogin(name, password, useragent, challenge)
86 headers['authorization'] = 'GoogleLogin Auth=' + auth
87 else:
88 # Obviously you could check for 'basic' in challenge and
89 # do Basic auth here. Similarly for Digest.
90 pass
91
92 # Now try the request again, this time with the authorization header in place.
93 conn.request("GET", "/feeds/default/blogs", headers=headers)
94 r = conn.getresponse()
95 print r.status
96 print r.read()