diff --git a/keystone/oauth1/controllers.py b/keystone/oauth1/controllers.py index d774cd7770..4e7413bf53 100644 --- a/keystone/oauth1/controllers.py +++ b/keystone/oauth1/controllers.py @@ -18,6 +18,7 @@ from oslo_log import log from oslo_serialization import jsonutils from oslo_utils import timeutils from six.moves import http_client +from six.moves.urllib import parse as urlparse from keystone.common import controller from keystone.common import dependency @@ -214,6 +215,15 @@ class OAuthControllerV3(controller.V3Controller): collection_name = 'not_used' member_name = 'not_used' + def _update_url_scheme(self, request): + """Update request url scheme with base url scheme.""" + url = self.base_url(request.context_dict, request.context_dict['path']) + url_scheme = list(urlparse.urlparse(url))[0] + req_url_list = list(urlparse.urlparse(request.url)) + req_url_list[0] = url_scheme + req_url = urlparse.urlunparse(req_url_list) + return req_url + def create_request_token(self, request): oauth_headers = oauth1.get_oauth_headers(request.headers) consumer_id = oauth_headers.get('oauth_consumer_key') @@ -230,13 +240,14 @@ class OAuthControllerV3(controller.V3Controller): self.resource_api.get_project(requested_project_id) self.oauth_api.get_consumer(consumer_id) + url = self._update_url_scheme(request) 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( - request.url, + url, http_method='POST', body=request.params, headers=req_headers) @@ -296,12 +307,13 @@ class OAuthControllerV3(controller.V3Controller): if now > expires: raise exception.Unauthorized(_('Request token is expired')) + url = self._update_url_scheme(request) access_verifier = oauth1.AccessTokenEndpoint( request_validator=validator.OAuthValidator(), token_generator=oauth1.token_generator) try: h, b, s = access_verifier.create_access_token_response( - request.url, + 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 235d0fe875..ee3bfd8452 100644 --- a/keystone/tests/unit/test_v3_oauth1.py +++ b/keystone/tests/unit/test_v3_oauth1.py @@ -22,6 +22,7 @@ from oslo_serialization import jsonutils from pycadf import cadftaxonomy from six.moves import http_client from six.moves import urllib +from six.moves.urllib import parse as urlparse import keystone.conf from keystone.contrib.oauth1 import routers @@ -646,6 +647,13 @@ class UUIDAuthTokenTests(AuthTokenTests, OAuthFlowTests): class MaliciousOAuth1Tests(OAuth1Tests): + def _switch_baseurl_scheme(self): + """Switch the base url scheme.""" + base_url_list = list(urlparse.urlparse(self.base_url)) + base_url_list[0] = 'https' if base_url_list[0] == 'http' else 'http' + bad_url = urlparse.urlunparse(base_url_list) + return bad_url + def test_bad_consumer_secret(self): consumer = self._create_single_consumer() consumer_id = consumer['id'] @@ -665,6 +673,17 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.post(url, headers=headers, expected_status=http_client.UNAUTHORIZED) + def test_bad_request_url_scheme(self): + consumer = self._create_single_consumer() + consumer_id = consumer['id'] + consumer_secret = consumer['secret'] + consumer = {'key': consumer_id, 'secret': consumer_secret} + bad_url_scheme = self._switch_baseurl_scheme() + url, headers = self._create_request_token(consumer, self.project_id, + base_url=bad_url_scheme) + 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'] @@ -758,7 +777,18 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.assertIn('Invalid signature', resp_data.get('error', {}).get('message')) - # 2. Invalid signature. + # 2. Invalid base url scheme. + # Update the base url scheme, so it will fail to validate signature. + bad_url_scheme = self._switch_baseurl_scheme() + url, headers = self._create_access_token(consumer, request_token, + base_url=bad_url_scheme) + 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')) + + # 3. 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) @@ -768,7 +798,7 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.assertIn('Invalid signature', resp_data.get('error', {}).get('message')) - # 3. Invalid verifier. + # 4. 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 @@ -783,7 +813,7 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.assertIn('Provided verifier', resp_data.get('error', {}).get('message')) - # 4. The provided consumer does not exist. + # 5. 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, @@ -792,7 +822,7 @@ class MaliciousOAuth1Tests(OAuth1Tests): self.assertIn('Provided consumer does not exist', resp_data.get('error', {}).get('message')) - # 5. The consumer key provided does not match stored consumer key. + # 6. 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)