Source code for keystone.api.validation

# 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.

"""API request/response validating middleware."""

import functools
import typing as ty

import flask
from oslo_serialization import jsonutils

from keystone.api.validation import validators


[docs] def validated(cls): cls._validated = True return cls
def _schema_validator( schema: ty.Dict[str, ty.Any], target: ty.Dict[str, ty.Any], args: ty.Any, kwargs: ty.Any, is_body: bool = True, ): """A helper method to execute JSON Schema Validation. This method checks the request version whether matches the specified ``max_version`` and ``min_version``. If the version range matches the request, we validate ``schema`` against ``target``. A failure will result in ``ValidationError`` being raised. :param schema: The JSON Schema schema used to validate the target. :param target: The target to be validated by the schema. :param args: Positional arguments which passed into original method. :param kwargs: Keyword arguments which passed into original method. :param is_body: Whether ``target`` is a HTTP request body or not. :returns: None. :raises: ``ValidationError`` if validation fails. """ schema_validator = validators._SchemaValidator(schema, is_body=is_body) schema_validator.validate(target)
[docs] def request_body_schema(schema: ty.Optional[ty.Dict[str, ty.Any]] = None): """Register a schema to validate request body. ``schema`` will be used for validating the request body just before the API method is executed. :param schema: The JSON Schema schema used to validate the target. If empty value is passed no validation will be performed. :param min_version: A string indicating the minimum API version ``schema`` applies against. :param max_version: A string indicating the maximum API version ``schema`` applies against. """ def add_validator(func): @functools.wraps(func) def wrapper(*args, **kwargs): if schema is not None: _schema_validator( schema, flask.request.get_json(silent=True, force=True) or {}, args, kwargs, is_body=True, ) return func(*args, **kwargs) wrapper._request_body_schema = schema return wrapper return add_validator
[docs] def request_query_schema(schema: ty.Optional[ty.Dict[str, ty.Any]] = None): """Register a schema to validate request query string parameters. ``schema`` will be used for validating request query strings just before the API method is executed. :param schema: The JSON Schema schema used to validate the target. If empty value is passed no validation will be performed. """ def add_validator(func): @functools.wraps(func) def wrapper(*args, **kwargs): if schema is not None: # NOTE: The request object is always the second argument. # However, numerous unittests pass in the request object # via kwargs instead so we handle that as well. # TODO(stephenfin): Fix unit tests so we don't have to do this if "req" in kwargs: req = kwargs["req"] else: req = flask.request.args _schema_validator(schema, req, args, kwargs, is_body=True) return func(*args, **kwargs) wrapper._request_query_schema = schema return wrapper return add_validator
[docs] def response_body_schema(schema: ty.Optional[ty.Dict[str, ty.Any]] = None): """Register a schema to validate response body. ``schema`` will be used for validating the response body just after the API method is executed. :param schema: The JSON Schema schema used to validate the target. If empty value is passed no validation will be performed. :param min_version: A string indicating the minimum API version ``schema`` applies against. :param max_version: A string indicating the maximum API version ``schema`` applies against. """ def add_validator(func): @functools.wraps(func) def wrapper(*args, **kwargs): response = func(*args, **kwargs) if schema is not None: # In Flask it is not uncommon that the method return a tuple of # body and the status code. In the runtime Keystone only return # a body, but some of the used testtools do return a tuple. if isinstance(response, tuple): _body = response[0] else: _body = response # NOTE(stephenfin): If our response is an object, we need to # serializer and deserialize to convert e.g. date-time # to strings _body = jsonutils.dump_as_bytes(_body) if _body == b"": body = None else: body = jsonutils.loads(_body) _schema_validator(schema, body, args, kwargs, is_body=True) return response wrapper._response_body_schema = schema return wrapper return add_validator