From f519f23638bfba51e60c58038945463a5bd01cac Mon Sep 17 00:00:00 2001 From: Niraj Singh Date: Thu, 2 May 2019 04:37:26 +0000 Subject: [PATCH] Jsonschema validation: base schema framework This patch adds a base jsonschema framework. This patch follows the Nova-Schema-framework: https://github.com/openstack/nova/tree/master/nova/api/validation Partial-Implements: blueprint tosca-csar-mgmt-driver Co-Author: Neha Alhat Change-Id: I6ffeaaffb92fa9949a439a049f3092878b884634 --- requirements.txt | 1 + tacker/api/schemas/__init__.py | 0 tacker/api/schemas/vnf_packages.py | 50 +++++++++++ tacker/api/validation/__init__.py | 51 +++++++++++ tacker/api/validation/parameter_types.py | 30 +++++++ tacker/api/validation/validators.py | 105 +++++++++++++++++++++++ tacker/common/exceptions.py | 4 + 7 files changed, 241 insertions(+) create mode 100644 tacker/api/schemas/__init__.py create mode 100644 tacker/api/schemas/vnf_packages.py create mode 100644 tacker/api/validation/__init__.py create mode 100644 tacker/api/validation/parameter_types.py create mode 100644 tacker/api/validation/validators.py diff --git a/requirements.txt b/requirements.txt index db4052bde..edf99c8e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ anyjson>=0.3.3 # BSD Babel!=2.4.0,>=2.3.4 # BSD eventlet!=0.23.0,!=0.25.0,>=0.22.0 # MIT requests>=2.14.2 # Apache-2.0 +jsonschema>=2.6.0 # MIT keystonemiddleware>=4.17.0 # Apache-2.0 kombu!=4.0.2,>=4.0.0 # BSD netaddr>=0.7.18 # BSD diff --git a/tacker/api/schemas/__init__.py b/tacker/api/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/api/schemas/vnf_packages.py b/tacker/api/schemas/vnf_packages.py new file mode 100644 index 000000000..4e966d5be --- /dev/null +++ b/tacker/api/schemas/vnf_packages.py @@ -0,0 +1,50 @@ +# Copyright (C) 2019 NTT DATA +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Schema for vnf packages create API. + +""" + +from tacker.api.validation import parameter_types + +create = { + 'type': 'object', + 'properties': { + 'userDefinedData': parameter_types.keyvalue_pairs + }, + 'additionalProperties': False, +} + +upload_from_uri = { + 'type': 'object', + 'properties': { + 'addressInformation': { + 'type': 'string', 'minLength': 0, + 'maxLength': 2048, 'format': 'uri' + }, + 'userName': { + 'type': 'string', 'maxLength': 255, + 'pattern': '^[a-zA-Z0-9-_]*$' + }, + 'password': { + # Allow to specify any string for strong password. + 'type': 'string', 'maxLength': 255, + }, + + }, + 'required': ['addressInformation'], + 'additionalProperties': False, +} diff --git a/tacker/api/validation/__init__.py b/tacker/api/validation/__init__.py new file mode 100644 index 000000000..9e0922db3 --- /dev/null +++ b/tacker/api/validation/__init__.py @@ -0,0 +1,51 @@ +# Copyright (C) 2019 NTT DATA +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Request Body validating middleware. + +""" + +import functools +import webob + +from tacker.api.validation import validators + + +def schema(request_body_schema): + """Register a schema to validate request body. + + Registered schema will be used for validating request body just before + API method executing. + + :param dict request_body_schema: a schema to validate request body + + """ + + def add_validator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + schema_validator = validators._SchemaValidator( + request_body_schema) + try: + schema_validator.validate(kwargs['body']) + except KeyError: + raise webob.exc.HTTPBadRequest( + explanation=_("Malformed request body")) + + return func(*args, **kwargs) + return wrapper + + return add_validator diff --git a/tacker/api/validation/parameter_types.py b/tacker/api/validation/parameter_types.py new file mode 100644 index 000000000..d0a86d95a --- /dev/null +++ b/tacker/api/validation/parameter_types.py @@ -0,0 +1,30 @@ +# Copyright (C) 2019 NTT DATA +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Common parameter types for validating request Body. + +""" + + +keyvalue_pairs = { + 'type': 'object', + 'patternProperties': { + '^[a-zA-Z0-9-_:. /]{1,255}$': { + 'type': 'string', 'maxLength': 255 + } + }, + 'additionalProperties': False +} diff --git a/tacker/api/validation/validators.py b/tacker/api/validation/validators.py new file mode 100644 index 000000000..3552b92b4 --- /dev/null +++ b/tacker/api/validation/validators.py @@ -0,0 +1,105 @@ +# Copyright (C) 2019 NTT DATA +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Internal implementation of request Body validating middleware. + +""" + +import jsonschema +from jsonschema import exceptions as jsonschema_exc +import rfc3986 +import six + +from tacker.common import exceptions as exception + + +@jsonschema.FormatChecker.cls_checks('uri') +def _validate_uri(instance): + return rfc3986.is_valid_uri(instance, require_scheme=True, + require_authority=True) + + +class FormatChecker(jsonschema.FormatChecker): + """A FormatChecker can output the message from cause exception + + We need understandable validation errors messages for users. When a + custom checker has an exception, the FormatChecker will output a + readable message provided by the checker. + """ + + def check(self, param_value, format): + """Check whether the param_value conforms to the given format. + + :argument param_value: the param_value to check + :type: any primitive type (str, number, bool) + :argument str format: the format that param_value should conform to + :raises: :exc:`FormatError` if param_value does not conform to format + """ + + if format not in self.checkers: + return + + # For safety reasons custom checkers can be registered with + # allowed exception types. Anything else will fall into the + # default formatter. + func, raises = self.checkers[format] + result, cause = None, None + + try: + result = func(param_value) + except raises as e: + cause = e + if not result: + msg = "%r is not a %r" % (param_value, format) + raise jsonschema_exc.FormatError(msg, cause=cause) + + +class _SchemaValidator(object): + """A validator class + + This class is changed from Draft4Validator to validate minimum/maximum + value of a string number(e.g. '10'). This changes can be removed when + we tighten up the API definition and the XML conversion. + Also FormatCheckers are added for checking data formats which would be + passed through cinder api commonly. + + """ + validator_org = jsonschema.Draft4Validator + + def __init__(self, schema): + validator_cls = jsonschema.validators.extend(self.validator_org, + validators={}) + format_checker = FormatChecker() + self.validator = validator_cls(schema, format_checker=format_checker) + + def validate(self, *args, **kwargs): + try: + self.validator.validate(*args, **kwargs) + except jsonschema.ValidationError as ex: + if len(ex.path) > 0: + detail = _("Invalid input for field/attribute %(path)s." + " Value: %(value)s. %(message)s") % { + 'path': ex.path.pop(), 'value': ex.instance, + 'message': ex.message + } + else: + detail = ex.message + raise exception.ValidationError(detail=detail) + except TypeError as ex: + # NOTE: If passing non string value to patternProperties parameter, + # TypeError happens. Here is for catching the TypeError. + detail = six.text_type(ex) + raise exception.ValidationError(detail=detail) diff --git a/tacker/common/exceptions.py b/tacker/common/exceptions.py index 6e7e1df72..0a1b23221 100644 --- a/tacker/common/exceptions.py +++ b/tacker/common/exceptions.py @@ -195,3 +195,7 @@ class DuplicateResourceName(TackerException): class DuplicateEntity(Conflict): message = _("%(_type)s already exist with given %(entry)s") + + +class ValidationError(BadRequest): + message = "%(detail)s"