From 59e4b4923fb9d3d9a4cd9a3de4658abd91125fc0 Mon Sep 17 00:00:00 2001 From: Hemanth Nakkina Date: Tue, 2 May 2017 16:44:21 +0530 Subject: [PATCH] Change url passed to oauth signature verifier to request url OAUTH signature verification should happen with the same URL used for signing. Typically at the user end it should be signed with the request URL and hence it should be verified with the same. Currently keystone uses public endpoint URL for signature verification. Modified the URL passed to oauth signature verification to request URL. Change-Id: I28059a43cb0088c2952c19f696042ebec54d26c9 Partial-Bug: #1687593 (cherry picked from commit 926685c5a4823d7e3ab3879bae1529052fff7d68) --- keystone/oauth1/controllers.py | 8 ++--- keystone/tests/unit/test_v3_oauth1.py | 42 ++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/keystone/oauth1/controllers.py b/keystone/oauth1/controllers.py index 6dffa6a82e..288224bfcb 100644 --- a/keystone/oauth1/controllers.py +++ b/keystone/oauth1/controllers.py @@ -233,15 +233,13 @@ class OAuthControllerV3(controller.V3Controller): self.resource_api.get_project(requested_project_id) self.oauth_api.get_consumer(consumer_id) - url = self.base_url(request.context_dict, request.context_dict['path']) - req_headers = {'Requested-Project-Id': requested_project_id} req_headers.update(request.headers) request_verifier = oauth1.RequestTokenEndpoint( request_validator=validator.OAuthValidator(), token_generator=oauth1.token_generator) h, b, s = request_verifier.create_request_token_response( - url, + request.url, http_method='POST', body=request.params, headers=req_headers) @@ -301,14 +299,12 @@ class OAuthControllerV3(controller.V3Controller): if now > expires: raise exception.Unauthorized(_('Request token is expired')) - url = self.base_url(request.context_dict, request.context_dict['path']) - access_verifier = oauth1.AccessTokenEndpoint( request_validator=validator.OAuthValidator(), token_generator=oauth1.token_generator) try: h, b, s = access_verifier.create_access_token_response( - url, + request.url, http_method='POST', body=request.params, headers=request.headers) diff --git a/keystone/tests/unit/test_v3_oauth1.py b/keystone/tests/unit/test_v3_oauth1.py index 352c0ecd88..6f8071b3ce 100644 --- a/keystone/tests/unit/test_v3_oauth1.py +++ b/keystone/tests/unit/test_v3_oauth1.py @@ -60,19 +60,21 @@ class OAuth1Tests(test_v3.RestfulTestCase): body={'consumer': ref}) return resp.result['consumer'] - def _create_request_token(self, consumer, project_id): + def _create_request_token(self, consumer, project_id, base_url=None): endpoint = '/OS-OAUTH1/request_token' client = oauth1.Client(consumer['key'], client_secret=consumer['secret'], signature_method=oauth1.SIG_HMAC, callback_uri="oob") headers = {'requested_project_id': project_id} - url, headers, body = client.sign(self.base_url + endpoint, + if not base_url: + base_url = self.base_url + url, headers, body = client.sign(base_url + endpoint, http_method='POST', headers=headers) return endpoint, headers - def _create_access_token(self, consumer, token): + def _create_access_token(self, consumer, token, base_url=None): endpoint = '/OS-OAUTH1/access_token' client = oauth1.Client(consumer['key'], client_secret=consumer['secret'], @@ -80,7 +82,9 @@ class OAuth1Tests(test_v3.RestfulTestCase): resource_owner_secret=token.secret, signature_method=oauth1.SIG_HMAC, verifier=token.verifier) - url, headers, body = client.sign(self.base_url + endpoint, + if not base_url: + base_url = self.base_url + url, headers, body = client.sign(base_url + endpoint, http_method='POST') headers.update({'Content-Type': 'application/json'}) return endpoint, headers @@ -638,6 +642,17 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.post(url, headers=headers, expected_status=http_client.UNAUTHORIZED) + def test_bad_request_url(self): + consumer = self._create_single_consumer() + consumer_id = consumer['id'] + consumer_secret = consumer['secret'] + consumer = {'key': consumer_id, 'secret': consumer_secret} + bad_base_url = 'http://localhost/identity_admin/v3' + url, headers = self._create_request_token(consumer, self.project_id, + base_url=bad_base_url) + self.post(url, headers=headers, + expected_status=http_client.UNAUTHORIZED) + def test_bad_request_token_key(self): consumer = self._create_single_consumer() consumer_id = consumer['id'] @@ -720,7 +735,18 @@ class MaliciousOAuth1Tests(OAuth1Tests): verifier = resp.result['token']['oauth_verifier'] request_token.set_verifier(verifier) - # 1. Invalid signature. + # 1. Invalid base url. + # Update the base url, so it will fail to validate the signature. + base_url = 'http://localhost/identity_admin/v3' + url, headers = self._create_access_token(consumer, request_token, + base_url=base_url) + resp = self.post(url, headers=headers, + expected_status=http_client.UNAUTHORIZED) + resp_data = jsonutils.loads(resp.body) + self.assertIn('Invalid signature', + resp_data.get('error', {}).get('message')) + + # 2. Invalid signature. # Update the secret, so it will fail to validate the signature. consumer.update({'secret': uuid.uuid4().hex}) url, headers = self._create_access_token(consumer, request_token) @@ -730,7 +756,7 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.assertIn('Invalid signature', resp_data.get('error', {}).get('message')) - # 2. Invalid verifier. + # 3. Invalid verifier. # Even though the verifier is well formatted, it is not verifier # that is stored in the backend, this is different with the testcase # above `test_bad_verifier` where it test that `verifier` is not @@ -745,7 +771,7 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.assertIn('Provided verifier', resp_data.get('error', {}).get('message')) - # 3. The provided consumer does not exist. + # 4. The provided consumer does not exist. consumer.update({'key': uuid.uuid4().hex}) url, headers = self._create_access_token(consumer, request_token) resp = self.post(url, headers=headers, @@ -754,7 +780,7 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.assertIn('Provided consumer does not exist', resp_data.get('error', {}).get('message')) - # 4. The consumer key provided does not match stored consumer key. + # 5. The consumer key provided does not match stored consumer key. consumer2 = self._create_single_consumer() consumer.update({'key': consumer2['id']}) url, headers = self._create_access_token(consumer, request_token)