Request middleware conditionally require content-type
Change to the YAMLTranslator middleware to only require content-type when the request should require it. GET and DELETE should not, and PUT and POST should only require it when there is a message body. Change-Id: I8614ff077c4fc5b19b77e12a468e053c76733487
This commit is contained in:
parent
ea727e7bbf
commit
7ff9a3b1ce
@ -118,20 +118,33 @@ class YAMLTranslator(HookableMiddlewareMixin, object):
|
||||
def process_request(self, req, resp):
|
||||
"""Performs content type enforcement on behalf of REST verbs."""
|
||||
valid_content_types = ['application/x-yaml']
|
||||
content_type = (req.content_type.split(';', 1)[0].strip()
|
||||
|
||||
# GET and DELETE should never carry a message body, and have
|
||||
# no content type. Check for content-length or
|
||||
# transfer-encoding to determine if a content-type header
|
||||
# is required.
|
||||
requires_content_type = (
|
||||
req.method not in ['GET', 'DELETE'] and (
|
||||
req.content_length is not None or
|
||||
req.get_header('transfer-encoding') is not None
|
||||
)
|
||||
)
|
||||
|
||||
if requires_content_type:
|
||||
content_type = (req.content_type.split(';', 1)[0].strip()
|
||||
if req.content_type else '')
|
||||
|
||||
if not content_type:
|
||||
raise falcon.HTTPMissingHeader('Content-Type')
|
||||
elif content_type not in valid_content_types:
|
||||
message = (
|
||||
"Unexpected content type: {type}. Expected content types "
|
||||
"are: {expected}."
|
||||
).format(
|
||||
type=six.b(req.content_type).decode('utf-8'),
|
||||
expected=valid_content_types
|
||||
)
|
||||
raise falcon.HTTPUnsupportedMediaType(description=message)
|
||||
if not content_type:
|
||||
raise falcon.HTTPMissingHeader('Content-Type')
|
||||
elif content_type not in valid_content_types:
|
||||
message = (
|
||||
"Unexpected content type: {type}. Expected content types "
|
||||
"are: {expected}."
|
||||
).format(
|
||||
type=six.b(req.content_type).decode('utf-8'),
|
||||
expected=valid_content_types
|
||||
)
|
||||
raise falcon.HTTPUnsupportedMediaType(description=message)
|
||||
|
||||
def process_response(self, req, resp, resource):
|
||||
"""Converts responses to ``application/x-yaml`` content type."""
|
||||
|
@ -12,6 +12,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from falcon import testing as falcon_testing
|
||||
|
||||
from deckhand import service
|
||||
@ -31,3 +34,13 @@ class BaseControllerTest(test_base.DeckhandWithDBTestCase,
|
||||
# NOTE: allow_anonymous_access allows these unit tests to get around
|
||||
# Keystone authentication.
|
||||
self.useFixture(fixtures.ConfPatcher(allow_anonymous_access=True))
|
||||
|
||||
def _read_data(self, file_name):
|
||||
# Reads data from a file in the resources directory
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
test_yaml_path = os.path.abspath(os.path.join(
|
||||
dir_path, os.pardir, 'resources', file_name + '.yaml'))
|
||||
|
||||
with open(test_yaml_path, 'r') as yaml_file:
|
||||
yaml_data = yaml_file.read()
|
||||
self.data = yaml.safe_load(yaml_data)
|
||||
|
@ -22,21 +22,85 @@ from deckhand.tests.unit.control import base as test_base
|
||||
class TestYAMLTranslator(test_base.BaseControllerTest):
|
||||
|
||||
def test_request_with_correct_content_type(self):
|
||||
rules = {'deckhand:create_cleartext_documents': '@'}
|
||||
self.policy.set_rules(rules)
|
||||
self._read_data('sample_document_simple')
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
headers={'Content-Type': 'application/x-yaml'},
|
||||
body=yaml.safe_dump(self.data),
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_request_empty_put_and_content_type(self):
|
||||
rules = {'deckhand:create_cleartext_documents': '@'}
|
||||
self.policy.set_rules(rules)
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
headers={'Content-Type': 'application/x-yaml'},
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_request_empty_put_and_no_content_type(self):
|
||||
rules = {'deckhand:create_cleartext_documents': '@'}
|
||||
self.policy.set_rules(rules)
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_request_zero_length_put_and_no_content_type(self):
|
||||
rules = {'deckhand:create_cleartext_documents': '@'}
|
||||
self.policy.set_rules(rules)
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
body='',
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_request_zero_length_put_and_content_type(self):
|
||||
rules = {'deckhand:create_cleartext_documents': '@'}
|
||||
self.policy.set_rules(rules)
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
headers={'Content-Type': 'application/x-yaml'},
|
||||
body='',
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_request_with_no_content_type_on_get(self):
|
||||
resp = self.app.simulate_get(
|
||||
'/versions', headers={'Content-Type': 'application/x-yaml'})
|
||||
'/versions',
|
||||
headers={})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_request_with_superfluous_content_type_on_get(self):
|
||||
resp = self.app.simulate_get(
|
||||
'/versions',
|
||||
headers={'Content-Type': 'application/x-yaml'},
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_request_with_correct_content_type_plus_encoding(self):
|
||||
resp = self.app.simulate_get(
|
||||
'/versions',
|
||||
headers={'Content-Type': 'application/x-yaml;encoding=utf-8'})
|
||||
rules = {'deckhand:create_cleartext_documents': '@'}
|
||||
self.policy.set_rules(rules)
|
||||
self._read_data('sample_document_simple')
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
headers={'Content-Type': 'application/x-yaml;encoding=utf-8'},
|
||||
body=yaml.safe_dump(self.data),
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
|
||||
class TestYAMLTranslatorNegative(test_base.BaseControllerTest):
|
||||
|
||||
def test_request_without_content_type_raises_exception(self):
|
||||
resp = self.app.simulate_get('/versions')
|
||||
self._read_data('sample_document_simple')
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
body=yaml.safe_dump(self.data),
|
||||
)
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
expected = {
|
||||
@ -60,8 +124,13 @@ class TestYAMLTranslatorNegative(test_base.BaseControllerTest):
|
||||
self.assertEqual(expected, yaml.safe_load(resp.content))
|
||||
|
||||
def test_request_with_invalid_content_type_raises_exception(self):
|
||||
resp = self.app.simulate_get(
|
||||
'/versions', headers={'Content-Type': 'application/json'})
|
||||
self._read_data('sample_document_simple')
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
body=yaml.safe_dump(self.data),
|
||||
)
|
||||
|
||||
self.assertEqual(415, resp.status_code)
|
||||
|
||||
expected = {
|
||||
@ -91,12 +160,16 @@ class TestYAMLTranslatorNegative(test_base.BaseControllerTest):
|
||||
"""Only application/x-yaml should be supported, not application/yaml,
|
||||
because it hasn't been registered as an official MIME type yet.
|
||||
"""
|
||||
resp = self.app.simulate_get(
|
||||
'/versions', headers={'Content-Type': 'application/yaml'})
|
||||
self._read_data('sample_document_simple')
|
||||
resp = self.app.simulate_put(
|
||||
'/api/v1.0/buckets/b1/documents',
|
||||
headers={'Content-Type': 'application/yaml'},
|
||||
body=yaml.safe_dump(self.data),
|
||||
)
|
||||
self.assertEqual(415, resp.status_code)
|
||||
|
||||
expected = {
|
||||
'apiVersion': 'N/A',
|
||||
'apiVersion': mock.ANY,
|
||||
'code': '415 Unsupported Media Type',
|
||||
'details': {
|
||||
'errorCount': 1,
|
||||
|
17
deckhand/tests/unit/resources/sample_document_simple.yaml
Normal file
17
deckhand/tests/unit/resources/sample_document_simple.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
schema: armada/Manifest/v1.0
|
||||
metadata:
|
||||
schema: metadata/Document/v1.0
|
||||
name: cluster-bootstrap
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: site
|
||||
data:
|
||||
release_prefix: ucp
|
||||
chart_groups:
|
||||
- kubernetes-proxy
|
||||
- container-networking
|
||||
- dns
|
||||
- kubernetes
|
||||
- kubernetes-rbac
|
||||
...
|
Loading…
x
Reference in New Issue
Block a user