I spent a whole day on coding this, after several failed attempts over the past few weeks. This only gets you as far as the first step, but it does so without any external libraries. And yes, I know it's close to two years after the OP, but from what I could see it still needed to be done.
#!/usr/bin/python 'demo Google OAuth' import sys, os, urllib, urllib2, time, httplib import hmac, hashlib, random, re, base64 PARAMETERS = { 'oauth_consumer_key': os.getenv('OAUTH_CONSUMER_KEY') or 'anonymous', 'oauth_signature_method': 'HMAC-SHA1', 'oauth_signature': '', 'oauth_timestamp': os.getenv('OAUTH_TIMESTAMP') or '%d' % time.time(), 'oauth_nonce': os.getenv('OAUTH_NONCE') or '%x' % random.getrandbits(64), 'oauth_version': '1.0', 'oauth_callback': os.getenv('OAUTH_CALLBACK') or 'callback', } SCOPE = {'scope': 'https://www.google.com/calendar/feeds/'} SECRET = os.getenv('OAUTH_CONSUMER_SECRET') or 'anonymous' def google_oauth(): 'OAuthGetRequestToken, OAuthAuthorizeToken, OAuthGetAccessToken' request_token = get_request_token() return request_token def get_request_token(): 'ask Google for a request token' url = 'https://www.google.com/accounts/OAuthGetRequestToken' token_secret = '' # we don't have a token secret yet PARAMETERS['oauth_signature'] = sign('&'.join((SECRET, token_secret)), '&'.join(map(urlencode, ('GET', url, parameters('signing'))))) body = urllib.urlencode(SCOPE) request = urllib2.Request(url + '?' + body) request.add_header('Authorization', 'OAuth ' + parameters('header')) opener = urllib2.build_opener(urllib2.HTTPSHandler(debuglevel = 1)) response = opener.open(request) reply = response.read() response.close() return reply def byte_encode(match): 'for use with re.sub' return '%%%02X' % ord(match.group()) def urlencode(string): "unreserved = ALPHA, DIGIT, '-', '.', '_', '~'" return re.sub(re.compile('[^0-9A-Za-z._~-]'), byte_encode, string.encode('utf8')) def sign(secret, text): print >>sys.stderr, 'signature base string: "%s", secret: %s' % ( repr(text), repr(secret)) digest = hmac.new(secret, text, hashlib.sha1).digest() return urlencode(base64.encodestring(digest).rstrip()) def base64string(hexstring): recoded = urlencode(base64.encodestring(hexstring.decode('hex')).rstrip()) print >>sys.stderr, 'recoded:', recoded return recoded def parameters(format): if format == 'header': formatted = ', '.join(['%s="%s"' % (key, value) for key, value in PARAMETERS.items()]) elif format == 'signing': formatted = '&'.join(sorted(['%s=%s' % (key, urlencode(value.encode('utf8'))) for key, value in (PARAMETERS.items() + SCOPE.items()) if key not in ['oauth_signature']])) #print >>sys.stderr, format, formatted return formatted def hmac_sha1_test(): 'from tools.ietf.org/html/rfc2202' assert sign('\x0b' * 20, 'Hi There') == base64string( 'b617318655057264e28bc0b6fb378c8ef146be00') assert sign('Jefe', 'what do ya want for nothing?') == base64string( 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79') assert sign('\xaa' * 20, '\xdd' * 50) == base64string( '125d7342b9ac11cd91a39af48aa17b4f63f175d3') # last test from http://oauth.net/core/1.0/#rfc.section.9.1.1, app. A.5.2 assert sign('kd94hf93k423kf44&pfkkdhi9sl3r4s00', 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26' + \ 'oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3D' + \ 'kllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26' + \ 'oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26' + \ 'oauth_version%3D1.0%26size%3Doriginal') == urlencode( 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=') return True if __name__ == '__main__': command = os.path.splitext(os.path.basename(sys.argv[0]))[0] print eval(command)(*sys.argv[1:])
Save it as google_oauth.py, and you can link to it like so:
ln -s google_oauth.py hmac_sha1_test.py
to test any of the subroutines. Combined with the use of environment variables, you can compare your results with those of Google's OAuth Playground (other folks here provided the link) and see where you are going wrong. I found many problems with the script that way; there may well be many more. But if you invoke ./google_oauth.py, you should see something like this:
jcomeau@intrepid:~/rentacoder/marchie$ ./google_oauth.py signature base string: "'GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_callback%3Dcallback%26oauth_consumer_key%3Danonymous%26oauth_nonce%3Da64720fda018906b%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1302253695%26oauth_version%3D1.0%26scope%3Dhttps%253A%252F%252Fwww.google.com%252Fcalendar%252Ffeeds%252F'", secret: 'anonymous&' send: 'GET /accounts/OAuthGetRequestToken?scope=https%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F HTTP/1.1\r\nAccept-Encoding: identity\r\nHost: www.google.com\r\nConnection: close\r\nAuthorization: OAuth oauth_nonce="a64720fda018906b", oauth_timestamp="1302253695", oauth_consumer_key="anonymous", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_signature="LSJxopFXWN71sTSIBIkNeGgsOjc%3D", oauth_callback="callback"\r\nUser-Agent: Python-urllib/2.6\r\n\r\n' reply: 'HTTP/1.1 200 OK\r\n' header: Content-Type: text/plain; charset=UTF-8 header: Date: Fri, 08 Apr 2011 09:08:20 GMT header: Expires: Fri, 08 Apr 2011 09:08:20 GMT header: Cache-Control: private, max-age=0 header: X-Content-Type-Options: nosniff header: X-XSS-Protection: 1; mode=block header: Content-Length: 118 header: Server: GSE header: Connection: close oauth_token=4%2FfvSIWW9WBHXa_CjInpOf4FdNYhCj&oauth_token_secret=qhB1EGIKjL1pG9POF2ZOcQk3&oauth_callback_confirmed=true