Fix schema validation to use JSONSchema for empty entity

APIs such as region creation allow an empty entity (a random UUID will
be generated as the region in this case).

Keystone uses JSONShema for validation and will allow an empty entity
to be passed if the schema doesn't have `required` properties (as the
region creation schema allows).

The patch fixes the schema validation code to pass even an empty entity
to JSONSchema validation so that an API such as region create can pass
the validation and can be created successfully.

Change-Id: If3dd49af5e16bb3b741efa4573f9f8e9085ddd14
Closes-Bug: #1501740
(cherry picked from commit ba7973b869)
This commit is contained in:
Dave Chen 2015-10-20 15:44:32 +08:00 committed by Brant Knudson
parent 40d2fc2456
commit 01b89c6195
2 changed files with 35 additions and 26 deletions

View File

@ -28,8 +28,7 @@ def validated(request_body_schema, resource_to_validate):
:param request_body_schema: a schema to validate the resource reference
:param resource_to_validate: the reference to validate
:raises keystone.exception.ValidationError: if `resource_to_validate` is
not passed by or passed with an empty value (see wrapper method
below).
None. (see wrapper method below).
:raises TypeError: at decoration time when the expected resource to
validate isn't found in the decorated method's
signature
@ -49,15 +48,15 @@ def validated(request_body_schema, resource_to_validate):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if kwargs.get(resource_to_validate):
if (resource_to_validate in kwargs and
kwargs[resource_to_validate] is not None):
schema_validator.validate(kwargs[resource_to_validate])
else:
try:
resource = args[arg_index]
# If resource to be validated is empty, no need to do
# validation since the message given by jsonschema doesn't
# help in this case.
if resource:
# If the resource to be validated is not None but
# empty, it is possible to be validated by jsonschema.
if resource is not None:
schema_validator.validate(resource)
else:
raise exception.ValidationError(

View File

@ -67,6 +67,12 @@ entity_create = {
'additionalProperties': True,
}
entity_create_optional_body = {
'type': 'object',
'properties': _entity_properties,
'additionalProperties': True,
}
entity_update = {
'type': 'object',
'properties': _entity_properties,
@ -122,12 +128,14 @@ class ValidatedDecoratorTests(unit.BaseTestCase):
'name': uuid.uuid4().hex,
}
invalid_entity = {}
@validation.validated(entity_create, 'entity')
def create_entity(self, entity):
"""Used to test cases where validated param is the only param."""
@validation.validated(entity_create_optional_body, 'entity')
def create_entity_optional_body(self, entity):
"""Used to test cases where there is an optional body."""
@validation.validated(entity_update, 'entity')
def update_entity(self, entity_id, entity):
"""Used to test cases where validated param is not the only param."""
@ -135,13 +143,9 @@ class ValidatedDecoratorTests(unit.BaseTestCase):
def test_calling_create_with_valid_entity_kwarg_succeeds(self):
self.create_entity(entity=self.valid_entity)
@expected_validation_failure
def test_calling_create_with_invalid_entity_kwarg_fails(self):
self.create_entity(entity=self.invalid_entity)
@expected_validation_failure
def test_calling_create_with_empty_entity_kwarg_fails(self):
self.create_entity(entity={})
def test_calling_create_with_empty_entity_kwarg_succeeds(self):
"""Test the case when client passing in an empty kwarg reference."""
self.create_entity_optional_body(entity={})
@expected_validation_failure
def test_calling_create_with_kwarg_as_None_fails(self):
@ -150,13 +154,9 @@ class ValidatedDecoratorTests(unit.BaseTestCase):
def test_calling_create_with_valid_entity_arg_succeeds(self):
self.create_entity(self.valid_entity)
@expected_validation_failure
def test_calling_create_with_invalid_entity_arg_fails(self):
self.create_entity(self.invalid_entity)
@expected_validation_failure
def test_calling_create_with_empty_entity_arg_fails(self):
self.create_entity({})
def test_calling_create_with_empty_entity_arg_succeeds(self):
"""Test the case when client passing in an empty entity reference."""
self.create_entity_optional_body({})
@expected_validation_failure
def test_calling_create_with_entity_arg_as_None_fails(self):
@ -180,9 +180,14 @@ class ValidatedDecoratorTests(unit.BaseTestCase):
def test_calling_update_with_valid_entity_succeeds(self):
self.update_entity(uuid.uuid4().hex, self.valid_entity)
@expected_validation_failure
def test_calling_update_with_invalid_entity_fails(self):
self.update_entity(uuid.uuid4().hex, self.invalid_entity)
def test_calling_update_with_empty_entity_kwarg_succeeds(self):
"""Test the case when client passing in an empty entity reference."""
global entity_update
original_entity_update = entity_update.copy()
# pop 'minProperties' from schema so that empty body is allowed.
entity_update.pop('minProperties')
self.update_entity(uuid.uuid4().hex, entity={})
entity_update = original_entity_update
class EntityValidationTestCase(unit.BaseTestCase):
@ -906,6 +911,11 @@ class RegionValidationTestCase(unit.BaseTestCase):
request_to_validate = {'other_attr': uuid.uuid4().hex}
self.create_region_validator.validate(request_to_validate)
def test_validate_region_create_succeeds_with_no_parameters(self):
"""Validate create region request with no parameters."""
request_to_validate = {}
self.create_region_validator.validate(request_to_validate)
def test_validate_region_update_succeeds(self):
"""Test that we validate a region update request."""
request_to_validate = {'id': 'us-west',