"""JSON Draft3 Schema Validator Wrapper. A Wrapper for a JSON Draft3 schema validation library which allows us to transform the results into an internal Response() format. """ from copy import deepcopy from datetime import datetime, time, timedelta from functools import wraps from flask import request from jsonschema import Draft3Validator from oto import response from oto.adaptors.flask import flaskify import pyraml.parser from ows_product_physical import config from ows_product_physical.constant import error from ows_product_physical.constant import header from ows_product_physical.validation.raml_utils import find_in_raml api_definition = pyraml.parser.load(config.API_DEFINITION_PATH) def date_is_after(date_to_check, time_delta): """Ensure date_to_check is at least some time delta after today. Args: date_to_check (string): Date in YYYY-MM-DD format. time_delta (int): Time delta to validate date against Returns: boolean: The response of the check """ _today = datetime.combine(datetime.today(), time(0, 0, 0)) _date = datetime.strptime(date_to_check, '%Y-%m-%d') return _date - _today >= timedelta(days=time_delta) def _format_error(validator, validator_value, message): return { 'validator': validator, 'validator_value': validator_value, 'message': message } def json_validator(schema): """Make validator from schema json. Args: schema (dict): Loaded json file """ return Draft3Validator(schema) def add_error_to_errors(errors, field, validator, validator_value, message): """Standardize validation error format. Args: errors (dict): errors to add error to field (string): field to add error for validator (string): type of validator that generated the error validator_value (any): type of value the validator expected message (string): a description of the error """ errors[field] = _format_error(validator, validator_value, message) return errors def validate(data, validator): """Generic JSON Draft3 Schema Validation. Args: data (dict): the JSON to be validated. validator (Draft3Validator): the JSON Draft3 Schema to validate against Returns: Response: the response of the create operation. """ errors = {} for e in sorted(validator.iter_errors(data), key=str): field = None if e.relative_path: field = e.relative_path.pop() elif e.absolute_schema_path: field = e.absolute_schema_path.pop() add_error_to_errors( errors, field, e.validator, e.validator_value, e.message) if errors: return response.create_error_response( code=error.VALIDATION_ERROR, message=errors, status=400) return response.Response(message={'status': 'ok'}, status=200) def raml_validate_tweaks(body_schema_tweaks): """A decorator to call when validating HTTP request body or headers. Returns: callable: the wrapped function. """ def decorator(f): @wraps(f) def _validate(*args, **kwargs): """Validate the request headers against a validator. Args: request (werkzeug.local.LocalProxy): the request object from the handler. validator (Draft3Validator): the JSON Draft3 Schema to validate against. Returns: Response: error HTTP response, or continue to the calling function if successful. """ method = request.method.lower() raml_section = find_in_raml( api_definition, request.url_rule.rule, method) # Validate headers. header_schema = raml_section.header_schema() header_data = dict(request.headers) validator = Draft3Validator(header_schema) validation_response = validate(header_data, validator) if not validation_response: return flaskify(validation_response) # If POST or PUT, validate body. if method == 'post' or method == 'put': body_schema = raml_section.body_schema() if body_schema: if body_schema_tweaks: body_schema = deepcopy(body_schema) body_schema_tweaks(body_schema) body_data = request.get_json() validator = Draft3Validator(body_schema) validation_response = validate(body_data, validator) if not validation_response: return flaskify(validation_response) return f(*args, **kwargs) return _validate return decorator raml_validate = raml_validate_tweaks(None) def reject_grass_headers(f): """Decorator that rejects grass headers. Returns: callable: the wrapped function. """ @wraps(f) def _validate(*args, **kwargs): account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) if account_type or account_id: return flaskify(response.Response( status=400, message=( 'Direct access through ows-grass is blocked. Only' ' non-ows-grass microservice-to-microservice requests' ' are allowed.') )) return f(*args, **kwargs) return _validate