Handle the exception from creating access token properly

If there is any request from client with any invalid request
parameters, invalid signature for example, keystone should
capture that and raise the exception.

It was `NotImplementedError`, `TypeError` thrown out and
presented directly to end user, and nothing helpful message
is given.

This patch fix that and show as many exception message that
is helpful for diagnosis as possible.

Change-Id: I112d0cd0c8a460c7b4d8d0e1c0b9c742aab9fde7
Closes-Bug: #1616424
This commit is contained in:
Dave Chen 2016-08-24 18:54:14 +08:00 committed by Dave Chen
parent a445165815
commit be5385c538
4 changed files with 166 additions and 23 deletions

View File

@ -14,6 +14,7 @@
"""Extensions supporting OAuth1."""
from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from six.moves import http_client
@ -33,6 +34,7 @@ from keystone.oauth1 import validator
CONF = keystone.conf.CONF
LOG = log.getLogger(__name__)
def _emit_user_oauth_consumer_token_invalidate(payload):
@ -301,30 +303,54 @@ class OAuthControllerV3(controller.V3Controller):
access_verifier = oauth1.AccessTokenEndpoint(
request_validator=validator.OAuthValidator(),
token_generator=oauth1.token_generator)
h, b, s = access_verifier.create_access_token_response(
url,
http_method='POST',
body=request.params,
headers=request.headers)
try:
h, b, s = access_verifier.create_access_token_response(
url,
http_method='POST',
body=request.params,
headers=request.headers)
except NotImplementedError:
# Client key or request token validation failed, since keystone
# does not yet support dummy client or dummy request token,
# so we will raise Unauthorized exception instead.
try:
self.oauth_api.get_consumer(consumer_id)
except exception.NotFound:
msg = _('Provided consumer does not exist.')
LOG.warning(msg)
raise exception.Unauthorized(message=msg)
if req_token['consumer_id'] != consumer_id:
msg = _('Provided consumer key does not match stored '
'consumer key.')
LOG.warning(msg)
raise exception.Unauthorized(message=msg)
# The response body is empty since either one of the following reasons
if not b:
if req_token['verifier'] != oauth_verifier:
msg = _('Provided verifier does not match stored verifier')
else:
msg = _('Invalid signature.')
LOG.warning(msg)
raise exception.Unauthorized(message=msg)
params = oauth1.extract_non_oauth_params(b)
# Invalid request would end up with the body like below:
# 'error=invalid_request&description=missing+resource+owner+key'
# Log this detail message so that we will know where is the
# validation failed.
if params:
msg = _('There should not be any non-oauth parameters')
if 'error' in params:
msg = _(
'Validation failed with errors: %(error)s, detail '
'message is: %(desc)s.') % {
'error': params['error'],
'desc': params['error_description']}
else:
msg = _('There should not be any non-oauth parameters.')
LOG.warning(msg)
raise exception.Unauthorized(message=msg)
if req_token['consumer_id'] != consumer_id:
msg = _('provided consumer key does not match stored consumer key')
raise exception.Unauthorized(message=msg)
if req_token['verifier'] != oauth_verifier:
msg = _('provided verifier does not match stored verifier')
raise exception.Unauthorized(message=msg)
if req_token['id'] != request_token_id:
msg = _('provided request key does not match stored request key')
raise exception.Unauthorized(message=msg)
if not req_token.get('authorizing_user_id'):
msg = _('Request Token does not have an authorizing user id')
msg = _('Request Token does not have an authorizing user id.')
LOG.warning(msg)
raise exception.Unauthorized(message=msg)
access_token_duration = CONF.oauth1.access_token_duration

View File

@ -101,7 +101,11 @@ class OAuthValidator(oauth1.RequestValidator):
def validate_request_token(self, client_key, token, request):
try:
return self.oauth_api.get_request_token(token) is not None
req_token = self.oauth_api.get_request_token(token)
if req_token:
return req_token['consumer_id'] == client_key
else:
return False
except exception.NotFound:
return False

View File

@ -13,6 +13,7 @@
# under the License.
import copy
import random
import uuid
import mock
@ -680,6 +681,7 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.post(url, headers=headers, expected_status=http_client.NOT_FOUND)
def test_bad_verifier(self):
self.config_fixture.config(debug=True, insecure_debug=True)
consumer = self._create_single_consumer()
consumer_id = consumer['id']
consumer_secret = consumer['secret']
@ -702,8 +704,77 @@ class MaliciousOAuth1Tests(OAuth1Tests):
request_token.set_verifier(uuid.uuid4().hex)
url, headers = self._create_access_token(consumer, request_token)
self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
resp = self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
resp_data = jsonutils.loads(resp.body)
self.assertIn('Validation failed with errors',
resp_data.get('error', {}).get('message'))
def test_validate_access_token_request_failed(self):
self.config_fixture.config(debug=True, insecure_debug=True)
consumer = self._create_single_consumer()
consumer_id = consumer['id']
consumer_secret = consumer['secret']
consumer = {'key': consumer_id, 'secret': consumer_secret}
url, headers = self._create_request_token(consumer, self.project_id)
content = self.post(
url, headers=headers,
response_content_type='application/x-www-form-urlencoded')
credentials = _urllib_parse_qs_text_keys(content.result)
request_key = credentials['oauth_token'][0]
request_secret = credentials['oauth_token_secret'][0]
request_token = oauth1.Token(request_key, request_secret)
url = self._authorize_request_token(request_key)
body = {'roles': [{'id': self.role_id}]}
resp = self.put(url, body=body, expected_status=http_client.OK)
verifier = resp.result['token']['oauth_verifier']
request_token.set_verifier(verifier)
# 1. 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)
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 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
# well formatted.
verifier = ''.join(random.SystemRandom().sample(base.VERIFIER_CHARS,
8))
request_token.set_verifier(verifier)
url, headers = self._create_access_token(consumer, request_token)
resp = self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
resp_data = jsonutils.loads(resp.body)
self.assertIn('Provided verifier',
resp_data.get('error', {}).get('message'))
# 3. 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,
expected_status=http_client.UNAUTHORIZED)
resp_data = jsonutils.loads(resp.body)
self.assertIn('Provided consumer does not exist',
resp_data.get('error', {}).get('message'))
# 4. 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)
resp = self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
resp_data = jsonutils.loads(resp.body)
self.assertIn('Provided consumer key',
resp_data.get('error', {}).get('message'))
def test_bad_authorizing_roles(self):
consumer = self._create_single_consumer()
@ -725,6 +796,40 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.admin_request(path=url, method='PUT',
body=body, expected_status=http_client.NOT_FOUND)
def test_no_authorizing_user_id(self):
consumer = self._create_single_consumer()
consumer_id = consumer['id']
consumer_secret = consumer['secret']
consumer = {'key': consumer_id, 'secret': consumer_secret}
url, headers = self._create_request_token(consumer, self.project_id)
content = self.post(
url, headers=headers,
response_content_type='application/x-www-form-urlencoded')
credentials = _urllib_parse_qs_text_keys(content.result)
request_key = credentials['oauth_token'][0]
request_secret = credentials['oauth_token_secret'][0]
request_token = oauth1.Token(request_key, request_secret)
url = self._authorize_request_token(request_key)
body = {'roles': [{'id': self.role_id}]}
resp = self.put(url, body=body, expected_status=http_client.OK)
verifier = resp.result['token']['oauth_verifier']
request_token.set_verifier(verifier)
request_token_created = self.oauth_api.get_request_token(
request_key.decode('utf-8'))
request_token_created.update({'authorizing_user_id': ''})
# Update the request token that is created instead of mocking
# the whole token object to focus on what's we want to test
# here and avoid any other factors that will result in the same
# exception.
with mock.patch.object(self.oauth_api,
'get_request_token') as mock_token:
mock_token.return_value = request_token_created
url, headers = self._create_access_token(consumer, request_token)
self.post(url, headers=headers,
expected_status=http_client.UNAUTHORIZED)
def test_expired_authorizing_request_token(self):
self.config_fixture.config(group='oauth1', request_token_duration=-1)

View File

@ -0,0 +1,8 @@
---
fixes:
- >
[`bug 1616424 <https://bugs.launchpad.net/keystone/+bug/1616424>`_]
Python build-in exception was raised if create request token or access token
request from client with invalid request parameters, invalid signature for example.
The implementation is hardened by showing proper exception and displaying the
failure reasons if existent.