| Summary: | 2 legged oauth requests from python-oauth2 get 401 response. | ||
|---|---|---|---|
| Product: | [Retired] CloudForms Cloud Engine | Reporter: | Steve Loranz <sloranz> |
| Component: | iwhd | Assignee: | Pete Zaitcev <zaitcev> |
| Status: | CLOSED DEFERRED | QA Contact: | wes hayutin <whayutin> |
| Severity: | high | Docs Contact: | |
| Priority: | unspecified | ||
| Version: | 1.0.0 | CC: | akarol, dajohnso, deltacloud-maint, dgao, nobody, ssachdev |
| Target Milestone: | rc | ||
| Target Release: | --- | ||
| Hardware: | Unspecified | ||
| OS: | Unspecified | ||
| Whiteboard: | |||
| Fixed In Version: | Doc Type: | Bug Fix | |
| Doc Text: | Story Points: | --- | |
| Clone Of: | Environment: | ||
| Last Closed: | 2011-10-12 23:07:51 UTC | Type: | --- |
| Regression: | --- | Mount Type: | --- |
| Documentation: | --- | CRM: | |
| Verified Versions: | Category: | --- | |
| oVirt Team: | --- | RHEL 7.3 requirements from Atomic Host: | |
| Cloudforms Team: | --- | Target Upstream Version: | |
> 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. |
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