Fix silently overwrite user specified content type

Previously, even user specified the response content type of api
'catalog/packages/<package_id>/ui', which will be silently replaced
by text/plain. To improve it, add list 'specific_content_types' to
make resource controller can specify different content type for each
action. And it will raise exception when user specified content type
is unsupported.

Change-Id: I5d56461333d730f0f9fbe715e9bb066e5b004203
Closes-Bug: #1476543
This commit is contained in:
Lin Yang 2015-08-24 17:03:48 +08:00
parent 788cd5aa7a
commit 1bc300dd36
4 changed files with 108 additions and 22 deletions

View File

@ -377,17 +377,12 @@ class Controller(object):
db_api.category_delete(category_id)
class PackageSerializer(wsgi.ResponseSerializer):
def serialize(self, action_result, accept, action):
if action == 'get_ui':
accept = 'text/plain'
elif action in ('download', 'get_logo', 'get_supplier_logo'):
accept = 'application/octet-stream'
return super(PackageSerializer, self).serialize(action_result,
accept,
action)
def create_resource():
serializer = PackageSerializer()
return wsgi.Resource(Controller(), serializer=serializer)
specific_content_types = {
'get_ui': ['text/plain'],
'download': ['application/octet-stream'],
'get_logo': ['application/octet-stream'],
'get_supplier_logo': ['application/octet-stream']}
deserializer = wsgi.RequestDeserializer(
specific_content_types=specific_content_types)
return wsgi.Resource(Controller(), deserializer=deserializer)

View File

@ -295,7 +295,8 @@ class Request(webob.Request):
'application/xml',
'application/octet-stream')
def best_match_content_type(self, supported_content_types=None):
def best_match_content_type(self, action, supported_content_types=None,
specific_content_types=None):
"""Determine the requested response content-type.
Based on the query extension then the Accept header.
@ -311,7 +312,11 @@ class Request(webob.Request):
if ctype in supported_content_types:
return ctype
bm = self.accept.best_match(supported_content_types)
if specific_content_types and action in specific_content_types:
bm = self.accept.best_match(specific_content_types[action])
else:
bm = self.accept.best_match(supported_content_types)
if not bm:
raise exceptions.UnsupportedContentType(content_type=self.accept)
return bm
@ -630,9 +635,10 @@ class RequestDeserializer(object):
"""Break up a Request object into more useful pieces."""
def __init__(self, body_deserializers=None, headers_deserializer=None,
supported_content_types=None):
supported_content_types=None, specific_content_types=None):
self.supported_content_types = supported_content_types
self.specific_content_types = specific_content_types
self.body_deserializers = {
'application/xml': XMLDeserializer(),
@ -660,7 +666,7 @@ class RequestDeserializer(object):
action_args.update(self.deserialize_headers(request, action))
action_args.update(self.deserialize_body(request, action))
accept = self.get_expected_content_type(request)
accept = self.get_expected_content_type(request, action)
return (action, action_args, accept)
@ -697,8 +703,10 @@ class RequestDeserializer(object):
except (KeyError, TypeError):
raise exceptions.UnsupportedContentType(content_type=content_type)
def get_expected_content_type(self, request):
return request.best_match_content_type(self.supported_content_types)
def get_expected_content_type(self, request, action):
return request.best_match_content_type(action,
self.supported_content_types,
self.specific_content_types)
def get_action_args(self, request_environment):
"""Parse dictionary created by routes library."""

View File

@ -210,13 +210,28 @@ class MuranoClient(rest_client.RestClient):
return self.delete('v1/catalog/packages/{0}'.format(id))
def download_package(self, id):
return self.get('v1/catalog/packages/{0}/download'.format(id))
headers = {
'X-Auth-Token': self.auth_provider.get_token(),
'content-type': 'application/octet-stream'
}
return self.get('v1/catalog/packages/{0}/download'.format(id),
headers=headers)
def get_ui_definition(self, id):
return self.get('v1/catalog/packages/{0}/ui'.format(id))
headers = {
'X-Auth-Token': self.auth_provider.get_token(),
'content-type': 'text/plain'
}
return self.get('v1/catalog/packages/{0}/ui'.format(id),
headers=headers)
def get_logo(self, id):
return self.get('v1/catalog/packages/{0}/logo'.format(id))
headers = {
'X-Auth-Token': self.auth_provider.get_token(),
'content-type': 'application/octet-stream'
}
return self.get('v1/catalog/packages/{0}/logo'.format(id),
headers=headers)
def list_categories(self):
resp, body = self.get('v1/catalog/packages/categories')

View File

@ -289,6 +289,74 @@ class TestCatalogApi(test_base.ControllerTest, test_base.MuranoApiTestCase):
result = req.get_response(self.api)
self.assertEqual(415, result.status_code)
self.assertTrue('Unsupported Content-Type' in result.body)
def test_get_ui_definition(self):
self._set_policy_rules(
{'get_package': '@'}
)
package_from_dir, package = self._test_package()
saved_package = db_catalog_api.package_upload(package, '')
self.expect_policy_check('get_package',
{'package_id': saved_package.id})
req = self._get_with_accept('/catalog/packages/%s/ui'
% saved_package.id,
accept="text/plain")
result = req.get_response(self.api)
self.assertEqual(200, result.status_code)
def test_get_ui_definition_negative(self):
package_from_dir, package = self._test_package()
saved_package = db_catalog_api.package_upload(package, '')
req = self._get_with_accept('/catalog/packages/%s/ui'
% saved_package.id,
accept='application/foo')
result = req.get_response(self.api)
self.assertEqual(415, result.status_code)
self.assertTrue('Unsupported Content-Type' in result.body)
def test_get_logo(self):
self._set_policy_rules(
{'get_package': '@'}
)
package_from_dir, package = self._test_package()
saved_package = db_catalog_api.package_upload(package, '')
self.expect_policy_check('get_package',
{'package_id': saved_package.id})
req = self._get_with_accept('/catalog/packages/%s/logo'
% saved_package.id,
accept="application/octet-stream")
result = req.get_response(self.api)
self.assertEqual(200, result.status_code)
self.assertEqual(package['logo'], result.body)
def test_get_logo_negative(self):
package_from_dir, package = self._test_package()
saved_package = db_catalog_api.package_upload(package, '')
req = self._get_with_accept('/catalog/packages/%s/logo'
% saved_package.id,
accept='application/foo')
result = req.get_response(self.api)
self.assertEqual(415, result.status_code)
self.assertTrue('Unsupported Content-Type' in result.body)
def test_add_public_unauthorized(self):
self._set_policy_rules({