Implement better validation for resource options

This implements validation for the resource options and exposes
definition of JSON Schema on the option object itself. Identity
Schema validation now utilizes the json_schema dict generated
by the registry of all options registered.

Change-Id: I2fe0fc432126ef4c2aff8951080a9b5798a67849
This commit is contained in:
Morgan Fainberg 2017-01-27 15:38:33 -08:00
parent 781db8e67a
commit 47cd72911c
4 changed files with 49 additions and 15 deletions

View File

@ -14,6 +14,7 @@
import six
from keystone.common import validation
from keystone.i18n import _
@ -21,6 +22,11 @@ def _validator(value):
return
def boolean_validator(value):
if value not in (True, False):
raise TypeError(_('Expected boolean value, got %r') % type(value))
def ref_mapper_to_dict_options(ref):
"""Convert the values in _resource_option_mapper to options dict.
@ -123,6 +129,22 @@ class ResourceOptionRegistry(object):
return option
return None
@property
def json_schema(self):
schema = {'type': 'object',
'properties': {},
'additionalProperties': False}
for opt in self.options:
if opt.json_schema is not None:
schema['properties'][opt.option_name] = validation.nullable(
opt.json_schema)
else:
# NOTE(notmorgan): without 'type' being specified, this
# can be of any-type. We are simply specifying no interesting
# values beyond that the property may exist here.
schema['properties'][opt.option_name] = {}
return schema
def register_option(self, option):
if option in self.options:
# Re-registering the exact same option does nothing.
@ -143,7 +165,8 @@ class ResourceOptionRegistry(object):
class ResourceOption(object):
def __init__(self, option_id, option_name, validator=_validator):
def __init__(self, option_id, option_name, validator=_validator,
json_schema_validation=None):
"""The base object to define the option(s) to be stored in the DB.
:param option_id: The ID of the option. This will be used to lookup
@ -164,6 +187,11 @@ class ResourceOption(object):
value to be persisted will be passed to it. No return
value is expected.
:type validator: callable
:param json_schema_validation: Dictionary defining the JSON schema
validation for the option itself. This
is used to generate the JSON Schema
validator(s) used at the API layer
:type json_schema_validation: dict
"""
if not isinstance(option_id, six.string_types) and len(option_id) == 4:
raise TypeError(_('`option_id` must be a string, got %r')
@ -179,6 +207,11 @@ class ResourceOption(object):
self._option_id = option_id
self._option_name = option_name
self.validator = validator
self._json_schema_validation = json_schema_validation
@property
def json_schema(self):
return self._json_schema_validation or None
@property
def option_name(self):

View File

@ -11,15 +11,22 @@
# under the License.
from keystone.common import resource_options
from keystone.common.validation import parameter_types
USER_OPTIONS_REGISTRY = resource_options.ResourceOptionRegistry('USER')
IGNORE_CHANGE_PASSWORD_OPT = (
resource_options.ResourceOption('1000',
'ignore_change_password_upon_first_use'))
resource_options.ResourceOption(
option_id='1000',
option_name='ignore_change_password_upon_first_use',
validator=resource_options.boolean_validator,
json_schema_validation=parameter_types.boolean))
IGNORE_PASSWORD_EXPIRY_OPT = (
resource_options.ResourceOption('1001',
'ignore_password_expiry'))
resource_options.ResourceOption(
option_id='1001',
option_name='ignore_password_expiry',
validator=resource_options.boolean_validator,
json_schema_validation=parameter_types.boolean))
# NOTE(notmorgan): wrap this in a function for testing purposes.

View File

@ -246,7 +246,9 @@ class User(sql.ModelBase, sql.DictBase):
for opt in cls.resource_options_registry.options:
if opt.option_name in options:
opt_value = options[opt.option_name]
opt.validator(opt_value)
# NOTE(notmorgan): None is always a valid type
if opt_value is not None:
opt.validator(opt_value)
resource_options[opt.option_id] = opt_value
user_obj = super(User, cls).from_dict(new_dict)
setattr(user_obj, '_resource_options', resource_options)

View File

@ -63,14 +63,6 @@ user_update_v2 = {
# Schema for Identity v3 API
_user_options = {
'type': 'object',
'properties': {
ro.IGNORE_CHANGE_PASSWORD_OPT.option_name: parameter_types.boolean,
},
'additionalProperties': False
}
_user_properties = {
'default_project_id': validation.nullable(parameter_types.id_string),
'description': validation.nullable(parameter_types.description),
@ -80,7 +72,7 @@ _user_properties = {
'password': {
'type': ['string', 'null']
},
'options': _user_options
'options': ro.USER_OPTIONS_REGISTRY.json_schema
}
# TODO(notmorgan): Provide a mechanism for options to supply real jsonschema