summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHemanth Nakkina <nh863p@att.com>2017-05-15 14:30:24 +0530
committerLance Bragstad <lbragstad@gmail.com>2017-07-13 22:10:13 +0000
commit215788d26db0649686ef9ba996a45f5d4c7c1f90 (patch)
treece3ec03fac1f84e2e89c071b8fe62819a485e896
parent59e4b4923fb9d3d9a4cd9a3de4658abd91125fc0 (diff)
downloadkeystone-215788d26db0649686ef9ba996a45f5d4c7c1f90.tar.gz
Change url scheme passed to oauth signature verifier
Change 461736 modifies the url passed to oauth signature verifier to request url. But in some deployments, https endpoints are terminated at haproxy and http request is sent to keystone. So request url will have http as url scheme whereas the endpoint is registered with https and signature at client is done with https url. This results in OAUTH signature validation failure. Update URL sent for OAUTH signature verification with the scheme of the base url. Change-Id: Iaba285985b616a35e3dfe33cdd45667174e7c69d Partial-Bug: #1687593 (cherry picked from commit b7aece57d2845fcfa45a84e6d21a6188ddd192cc)
-rw-r--r--keystone/oauth1/controllers.py16
-rw-r--r--keystone/tests/unit/test_v3_oauth1.py38
2 files changed, 48 insertions, 6 deletions
diff --git a/keystone/oauth1/controllers.py b/keystone/oauth1/controllers.py
index 288224bfc..3334eb997 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
@@ -217,6 +218,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')
@@ -233,13 +243,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)
@@ -299,12 +310,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 6f8071b3c..b15af13f1 100644
--- a/keystone/tests/unit/test_v3_oauth1.py
+++ b/keystone/tests/unit/test_v3_oauth1.py
@@ -21,6 +21,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 import exception
@@ -634,6 +635,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']
@@ -653,6 +661,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']
@@ -746,7 +765,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)
@@ -756,7 +786,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
@@ -771,7 +801,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,
@@ -780,7 +810,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)