Hide Forgot
Description of problem: Doing a simple test of python-oauth2 based client fails with 401. Version-Release number of selected component (if applicable): iwhd 0.99 How reproducible: Always Steps to Reproduce: 1. start warehouse: iwhd -v -o -U key:secret -p 9090 -c /etc/iwhd/conf.js -d localhost:27017 2. run the following in python import oauth2 as oauth consumer = oauth.Consumer('key', 'secret') client = oauth.Client(consumer, None) r, c = client.request('http://localhost:9090') print 'Response headers: %s\nContent: %s' % (r,c) Actual results: Response headers: {'date': 'Thu, 06 Oct 2011 13:02:56 GMT', 'status': '401', 'content-length': '0', 'www-authenticate': 'OAuth realm="chifac2"'} Content: Expected results: status header should be 200 and content should be: "<api service="image_warehouse" version="0.99">\n\t<link rel="bucket_factory" href="http://localhost:9090/_new"/>\n\t<link rel="provider_list" href="http://localhost:9090/_providers"/>\n</api>\n" Additional info: I was able to successfully get a request token from a third party test server as described at http://term.ie/oauth/example/ I am using HMAC-SHA1 as the signature method. Debugging what is expected by iwhd that I am failing to provide is tough when very little information is logged by iwhd even with verbose logging on: --- sample iwhd log --- primary store type is fs db is at localhost:27017 will listen on port 9090 my location is "primary" autheticated with OAuth >> HTTP user-agent: Python-httplib2/0.7.0 (gzip) accept-encoding: gzip, deflate Host: chifac2.usersys.redhat.com:9090 >> headers: 3 Oct 11 16:18:21 iwhd[30921]: parse_url: 6: _providers >> HTTP user-agent: Python-httplib2/0.7.0 (gzip) accept-encoding: gzip, deflate Host: chifac2.usersys.redhat.com:9090 >> headers: 3 Oct 11 16:41:34 iwhd[30921]: parse_url: 6: _providers
> user-agent: Python-httplib2/0.7.0 (gzip) > accept-encoding: gzip, deflate > Host: chifac2.usersys.redhat.com:9090 For authentication to work, an "Authorization:" header must be set, and have type "OAuth" with parameters. The client did not transmit it. Steve on the call promised tcpdump dumps, which is good, but I think we know that Python code needs some kind of tweaking in the client. OAuth needs Host: header in order to reconstruct the URL that is included into HMAC string. The header above looks good.
Here's what's getting sent over by python-oauth2: --- begin 2011-10-12 13:56:56.398409 IP (tos 0x0, ttl 59, id 8296, offset 0, flags [DF], proto TCP (6), length 413) redacted.50463 > redacted.websm: Flags [P.], cksum 0x9f0c (correct), seq 1:362, ack 1, win 32854, options [nop,nop,TS val 156687778 ecr 2421563146], length 361 E... h@.;.x. .q0 . ...#..T..x......V....... V...V. GET /_providers?oauth_body_hash=2jmj7l5rSw0yVb%2FvlWAYkK%2FYBwk%3D&oauth_nonce=39138052&oauth_timestamp=1318446499&oauth_consumer_key=key&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_signature=juADSgCmcKy5P9uRm8Ugw2HMA9c%3D HTTP/1.1 Host: redacted:9090 accept-encoding: gzip, deflate user-agent: Python-httplib2/0.7.0 (gzip) --- end So, you are correct that there is not an 'Authorization' header being sent here. Is that header actually part of a purely two legged flow or is it added for the first part of a three legged flow?
...and here is what the ruby lib that Matt is using is sending. The difference is obvious, just a question of how we want to deal with it. --- begin 2011-10-12 14:08:38.185909 IP (tos 0x0, ttl 61, id 17209, offset 0, flags [DF], proto TCP (6), length 412) redacted.55128 > redacted.websm: Flags [P.], cksum 0x1ba9 (correct), seq 1:361, ack 1, win 115, options [nop,nop,TS val 3014657576 ecr 2422264845], length 360 E...C9@.=..N ... . ..X#...Fn.......s....... GET / HTTP/1.1 Accept: */* Connection: close User-Agent: OAuth gem v0.4.4 Authorization: OAuth oauth_consumer_key="key", oauth_nonce="m14FD3Jxb16auOtdGPxmIIcIQQ6Z4EJ7uwcSN9VxA", oauth_signature="VHKpHSFelGkkXhdzLuEjArp1Va0%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1318447201", oauth_version="1.0" Host: redacted:9090 --- end
Well, this is unpleasant. We do not use query strings anywhere else in iwhd, on the account of them being non-RESTful, so processing them would require a certain amount of surgery. Is there an easy way to make the library to supply the header instead?
I've been able to force the headers, but now I'm getting a signature mismatch. *sigh* >> HTTP user-agent: Python-httplib2/0.7.0 (gzip) authorization: OAuth realm="", oauth_body_hash="2jmj7l5rSw0yVb%2FvlWAYkK%2FYBwk%3D", oauth_nonce="11996549", oauth_timestamp="1318456557", oauth_consumer_key="key", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_signature="0UCfXKdFAllfdRY8YWDX3R8Q4so%3D" accept-encoding: gzip, deflate Host: redacted:9090 >> headers: 4 Oct 12 16:44:32 iwhd[17795]: parse_url: 0: OAuth supplied [8] realm= oauth_body_hash=2jmj7l5rSw0yVb/vlWAYkK/YBwk= oauth_nonce=11996549 oauth_timestamp=1318456557 oauth_consumer_key=key oauth_signature_method=HMAC-SHA1 oauth_version=1.0 oauth_signature=0UCfXKdFAllfdRY8YWDX3R8Q4so= OAuth calculated http://redacted:9090/ oauth_consumer_key=key oauth_nonce=11996549 oauth_signature_method=HMAC-SHA1 oauth_timestamp=1318456557 oauth_version=1.0 oauth_signature=wq63fBvPRG9f2z31I/gfFXmwbqQ= Oct 12 16:44:32 iwhd[17795]: OAuth signature mismatch
Are you sure you did not supply both by accident? Query is included into the string to be signed. BTW, how did you "force" the headers? The source says: https://github.com/simplegeo/python-oauth2/blob/master/oauth2/__init__.py: def to_header(self, realm=''): ................ auth_header = 'OAuth realm="%s"' % realm if params_header: auth_header = "%s, %s" % (auth_header, params_header) return {'Authorization': auth_header} def to_url(self): base_url = urlparse.urlparse(self.url) query = base_url.query query = parse_qs(query) for k, v in self.items(): query.setdefault(k, []).append(v) ................ url = (scheme, netloc, path, params, urllib.urlencode(query, True), fragment) return urlparse.urlunparse(url) def request(self, uri, method="GET", body='', headers=None, redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): DEFAULT_POST_CONTENT_TYPE = 'application/x-www-form-urlencoded' ................ if is_form_encoded: body = req.to_postdata() elif method == "GET": uri = req.to_url() else: headers.update(req.to_header(realm=realm)) return httplib2.Http.request(self, uri, method=method, body=body, headers=headers, redirections=redirections, connection_type=connection_type) Looks like it always does query if method is GET.
'Forced' with the following... import oauth2 as oauth url = "http://redacted:9090/" consumer = oauth.Consumer(key='key', secret='secret') sig_method = oauth.SignatureMethod_HMAC_SHA1() params = {'oauth_version':oauth.OAUTH_VERSION, 'oauth_nonce':oauth.generate_nonce(), 'oauth_timestamp':oauth.generate_timestamp(), 'oauth_signature_method':sig_method.name, 'oauth_consumer_key':consumer.key} req = oauth.Request(method='GET', url=url, parameters=params) req.sign_request(sig_method, consumer, None) client = oauth.Client(consumer, None) client.set_signature_method(sig_method) r, c = client.request(url, headers=req.to_header()) print 'Response headers: %s\nContent: %s' % (r,c)
Okay... success, finally. What worked on the python side for future reference: --- example begin import oauth2 as oauth url = "http://redacted:9090/" consumer = oauth.Consumer(key='key', secret='secret') sig_method = oauth.SignatureMethod_HMAC_SHA1() params = {'oauth_version':oauth.OAUTH_VERSION, 'oauth_nonce':oauth.generate_nonce(), 'oauth_timestamp':oauth.generate_timestamp(), 'oauth_signature_method':sig_method.name, 'oauth_consumer_key':consumer.key} req = oauth.Request(method='GET', url=url, parameters=params) sig = sig_method.sign(req, consumer, None) req['oauth_signature'] = sig client = oauth.Client(consumer, None) r, c = client.request(url, headers=req.to_header()) print 'Response headers: %s\nContent: %s' % (r,c) --- example end I'll close this out, but it's probably worth noting that the OAuth 1.0 core spec does state that having this information as request GET parameters is one of three valid options (section 9.1.1 - http://oauth.net/core/1.0/#anchor14). You may run into this problem with other clients in the future.
Quite likely it was specified, maybe I was inattentive. I've only read the RFC anyway. This should be DEFERRER rather than NOTABUG, I think.