Add schema check for authorize request token

This patch add the schema check for authorize
request token API. It'll avoiding some 500 error
caused by invalid input format and it will raise
400 error correctly.

This patch also add role name support for
authorize request token

Closes-bug: #1736875
Change-Id: I9d113692702e7aaa0127ffa9405a17908c0c6ff7
This commit is contained in:
wangxiyuan 2017-12-07 14:51:39 +08:00
parent 40e75b528a
commit 1f0473a597
5 changed files with 106 additions and 8 deletions

View File

@ -370,6 +370,16 @@ class OAuthControllerV3(controller.V3Controller):
return response
def _normalize_role_list(self, authorize_roles):
roles = set()
for role in authorize_roles:
if role.get('id'):
roles.add(role['id'])
else:
roles.add(PROVIDERS.role_api.get_unique_role_by_name(
role['name'])['id'])
return roles
@controller.protected()
def authorize_request_token(self, request, request_token_id, roles):
"""An authenticated user is going to authorize a request token.
@ -379,6 +389,7 @@ class OAuthControllerV3(controller.V3Controller):
there is not another easy way to make sure the user knows which roles
are being requested before authorizing.
"""
validation.lazy_validate(schema.request_token_authorize, roles)
if request.context.is_delegated_auth:
raise exception.Forbidden(
_('Cannot authorize a request token'
@ -394,10 +405,7 @@ class OAuthControllerV3(controller.V3Controller):
if now > expires:
raise exception.Unauthorized(_('Request token is expired'))
# put the roles in a set for easy comparison
authed_roles = set()
for role in roles:
authed_roles.add(role['id'])
authed_roles = self._normalize_role_list(roles)
# verify the authorizing user has the roles
user_token = authorization.get_token_ref(request.context_dict)

View File

@ -32,3 +32,17 @@ consumer_update = {
'minProperties': 1,
'additionalProperties': True
}
request_token_authorize = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': parameter_types.id_string,
'name': parameter_types.name,
},
'minProperties': 1,
'maxProperties': 1,
'additionalProperties': False
}
}

View File

@ -35,7 +35,6 @@ from keystone.tests.unit.common import test_notifications
from keystone.tests.unit import ksfixtures
from keystone.tests.unit.ksfixtures import temporaryfile
from keystone.tests.unit import test_v3
from keystone.tests.unit import utils as test_utils
CONF = keystone.conf.CONF
@ -720,8 +719,6 @@ class MaliciousOAuth1Tests(OAuth1Tests):
body = {'roles': [{'id': self.role_id}]}
self.put(url, body=body, expected_status=http_client.NOT_FOUND)
@test_utils.wip('Waiting on validation to be added from fixing bug '
'1736875')
def test_bad_request_body_when_authorize(self):
consumer = self._create_single_consumer()
consumer_id = consumer['id']
@ -872,7 +869,7 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.assertIn('Provided consumer key',
resp_data.get('error', {}).get('message'))
def test_bad_authorizing_roles(self):
def test_bad_authorizing_roles_id(self):
consumer = self._create_single_consumer()
consumer_id = consumer['id']
consumer_secret = consumer['secret']
@ -892,6 +889,24 @@ class MaliciousOAuth1Tests(OAuth1Tests):
self.admin_request(path=url, method='PUT',
body=body, expected_status=http_client.NOT_FOUND)
def test_bad_authorizing_roles_name(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]
url = self._authorize_request_token(request_key)
body = {'roles': [{'name': 'fake_name'}]}
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']

View File

@ -2319,8 +2319,11 @@ class OAuth1ValidationTestCase(unit.BaseTestCase):
create = oauth1_schema.consumer_create
update = oauth1_schema.consumer_update
authorize = oauth1_schema.request_token_authorize
self.create_consumer_validator = validators.SchemaValidator(create)
self.update_consumer_validator = validators.SchemaValidator(update)
self.authorize_request_token_validator = validators.SchemaValidator(
authorize)
def test_validate_consumer_request_succeeds(self):
"""Test that we validate a consumer request successfully."""
@ -2363,6 +2366,53 @@ class OAuth1ValidationTestCase(unit.BaseTestCase):
self.create_consumer_validator.validate(request_to_validate)
self.update_consumer_validator.validate(request_to_validate)
def test_validate_authorize_request_token(self):
request_to_validate = [
{
"id": "711aa6371a6343a9a43e8a310fbe4a6f"
},
{
"name": "test_role"
}
]
self.authorize_request_token_validator.validate(request_to_validate)
def test_validate_authorize_request_token_with_additional_properties(self):
request_to_validate = [
{
"id": "711aa6371a6343a9a43e8a310fbe4a6f",
"fake_key": "fake_value"
}
]
self.assertRaises(exception.SchemaValidationError,
self.authorize_request_token_validator.validate,
request_to_validate)
def test_validate_authorize_request_token_with_id_and_name(self):
request_to_validate = [
{
"id": "711aa6371a6343a9a43e8a310fbe4a6f",
"name": "admin"
}
]
self.assertRaises(exception.SchemaValidationError,
self.authorize_request_token_validator.validate,
request_to_validate)
def test_validate_authorize_request_token_with_non_id_or_name(self):
request_to_validate = [
{
"fake_key": "fake_value"
}
]
self.assertRaises(exception.SchemaValidationError,
self.authorize_request_token_validator.validate,
request_to_validate)
class PasswordValidationTestCase(unit.TestCase):
def setUp(self):

View File

@ -0,0 +1,11 @@
---
fixes:
- >
[`bug 1736875 <https://bugs.launchpad.net/keystone/+bug/1736875>`_]
Add schema check to return a 400 status code instead of a 500 when
authorize a request token with non-id attributes in the `roles` parameter.
other:
- >
Keystone now supports authorizing a request token by providing a role name.
A `role` in the `roles` parameter can include either a role name or role
id, but not both.