示例#1
0
def validate_definition(definition, deref, def_name=None):
    definition = deref(definition)

    if 'allOf' in definition:
        for inner_definition in definition['allOf']:
            validate_definition(inner_definition, deref)
    else:
        required = definition.get('required', [])
        props = iterkeys(definition.get('properties', {}))
        extra_props = list(set(required) - set(props))
        if extra_props:
            raise SwaggerValidationError(
                "Required list has properties not defined: {}".format(
                    extra_props))

        validate_defaults_in_definition(definition, deref)

    if 'discriminator' in definition:
        required_props, not_required_props = get_collapsed_properties_type_mappings(
            definition, deref)
        discriminator = definition['discriminator']
        if discriminator not in required_props and discriminator not in not_required_props:
            raise SwaggerValidationError(
                'discriminator (%s) must be defined in properties' %
                discriminator)
        if discriminator not in required_props:
            raise SwaggerValidationError(
                'discriminator (%s) must be defined a required property' %
                discriminator)
        if required_props[discriminator] != 'string':
            raise SwaggerValidationError(
                'discriminator (%s) must be a string property' % discriminator)
示例#2
0
def get_validator(spec_json, origin='unknown'):
    """
    :param spec_json: Dict representation of the json API spec
    :param origin: filename or url of the spec - only use for error messages
    :return: module responsible for validation based on Swagger version in the
        spec
    """
    swagger12_version = spec_json.get('swaggerVersion')
    swagger20_version = spec_json.get('swagger')

    if swagger12_version and swagger20_version:
        raise SwaggerValidationError(
            "You've got conflicting keys for the Swagger version in your spec. "
            "Expected `swaggerVersion` or `swagger`, but not both.")
    elif swagger12_version and swagger12_version == '1.2':
        # we don't care about versions prior to 1.2
        return validator12
    elif swagger20_version and swagger20_version == '2.0':
        return validator20
    elif swagger12_version is None and swagger20_version is None:
        raise SwaggerValidationError(
            "Swagger spec {0} missing version. Expected "
            "`swaggerVersion` or `swagger`".format(origin))
    else:
        raise SwaggerValidationError(
            'Swagger version {0} not supported.'.format(swagger12_version
                                                        or swagger20_version))
示例#3
0
def validate_definition(definition, deref, def_name=None, visited_definitions_ids=None):
    """
    :param visited_definitions_ids: set of ids of already visited definitions (after dereference)
                                    This is used to cut recursion in case of recursive definitions
    :type visited_definitions_ids: set
    """
    definition = deref(definition)

    if visited_definitions_ids is not None:
        if id(definition) in visited_definitions_ids:
            return
        visited_definitions_ids.add(id(definition))

    swagger_type = definition.get('type')
    if isinstance(swagger_type, list):
        # not valid Swagger; see https://github.com/OAI/OpenAPI-Specification/issues/458
        raise SwaggerValidationError('In definition of {}, type must be a string; lists are not allowed ({})'.format(def_name or '(no name)', swagger_type))

    if 'allOf' in definition:
        for idx, inner_definition in enumerate(definition['allOf']):
            validate_definition(
                definition=inner_definition,
                deref=deref,
                def_name='{}/{}'.format(def_name, str(idx)),
                visited_definitions_ids=visited_definitions_ids,
            )
    else:
        required = definition.get('required', [])
        props = iterkeys(definition.get('properties', {}))
        extra_props = list(set(required) - set(props))
        if extra_props:
            raise SwaggerValidationError(
                "In definition of {}, required list has properties not defined: {}.".format(
                    def_name or '(no name)', extra_props,
                )
            )

        validate_defaults_in_definition(definition, deref)
        validate_arrays_in_definition(definition, def_name=def_name)

        for property_name, property_spec in iteritems(definition.get('properties', {})):
            validate_definition(
                definition=property_spec,
                deref=deref,
                def_name='{}/properties/{}'.format(def_name, property_name),
                visited_definitions_ids=visited_definitions_ids,
            )

    if 'discriminator' in definition:
        required_props, not_required_props = get_collapsed_properties_type_mappings(definition, deref)
        discriminator = definition['discriminator']
        if discriminator not in required_props and discriminator not in not_required_props:
            raise SwaggerValidationError('In definition of {}, discriminator ({}) must be defined in properties'.format(def_name or '(no name)', discriminator))
        if discriminator not in required_props:
            raise SwaggerValidationError('In definition of {}, discriminator ({}) must be a required property'.format(def_name or '(no name)', discriminator))
        if required_props[discriminator] != 'string':
            raise SwaggerValidationError('In definition of {}, discriminator ({}) must be a string property'.format(def_name or '(no name)', discriminator))
def validate_non_body_parameter(param, deref, def_name):
    if 'type' not in param:
        raise SwaggerValidationError(
            'Non-Body parameter in `{def_name}` does not specify `type`.'.
            format(def_name=def_name), )

    if param['type'] == 'array' and 'items' not in param:
        raise SwaggerValidationError(
            'Non-Body array parameter in `{def_name}` does not specify `items`.'
            .format(def_name=def_name), )
示例#5
0
def validate_operation(operation, model_ids):
    """Validate an Operation Object (§5.2.3)."""
    try:
        validate_data_type(operation, model_ids, allow_refs=False, allow_voids=True)
    except SwaggerValidationError as e:
        raise SwaggerValidationError(
            'Operation "{}": {}'.format(operation['nickname'], str(e)))

    for parameter in operation['parameters']:
        try:
            validate_parameter(parameter, model_ids)
        except SwaggerValidationError as e:
            raise SwaggerValidationError(
                'Operation "%s", parameter "%s": %s' %
                (operation['nickname'], parameter['name'], str(e)))
def validate_data_type(obj,
                       model_ids,
                       allow_arrays=True,
                       allow_voids=False,
                       allow_refs=True,
                       allow_file=False):
    """Validate an object that contains a data type (§4.3.3).

    Params:
    - obj: the dictionary containing the data type to validate
    - model_ids: a list of model ids
    - allow_arrays: whether an array is permitted in the data type.  This is
      used to prevent nested arrays.
    - allow_voids: whether a void type is permitted.  This is used when
      validating Operation Objects (§5.2.3).
    - allow_refs: whether '$ref's are permitted.  If true, then 'type's
      are not allowed to reference model IDs.
    """

    typ = obj.get('type')
    ref = obj.get('$ref')

    # TODO Use a custom jsonschema.Validator to Validate defaultValue
    # enum, minimum, maximum, uniqueItems
    if typ is not None:
        if typ in PRIMITIVE_TYPES:
            return
        if allow_voids and typ == 'void':
            return
        if typ == 'array':
            if not allow_arrays:
                raise SwaggerValidationError('"array" not allowed')
            # Items Object (§4.3.4)
            items = obj.get('items')
            if items is None:
                raise SwaggerValidationError('"items" not found')
            validate_data_type(items, model_ids, allow_arrays=False)
            return
        if typ == 'File':
            if not allow_file:
                raise SwaggerValidationError(
                    'Type "File" is only valid for form parameters')
            return
        if typ in model_ids:
            if allow_refs:
                raise SwaggerValidationError(
                    'must use "$ref" for referencing "%s"' % typ)
            return
        raise SwaggerValidationError('unknown type "%s"' % typ)

    if ref is not None:
        if not allow_refs:
            raise SwaggerValidationError('"$ref" not allowed')
        if ref not in model_ids:
            raise SwaggerValidationError('unknown model id "%s"' % ref)
        return

    raise SwaggerValidationError('no "$ref" or "type" present')
def validate_arrays_in_definition(definition_spec, def_name=None):
    if definition_spec.get(
            'type') == 'array' and 'items' not in definition_spec:
        raise SwaggerValidationError(
            'Definition of type array must define `items` property{}.'.format(
                ''
                if not def_name else ' (definition {})'.format(def_name), ), )
示例#8
0
def validate_model(model, model_name, model_ids):
    """Validate a Model Object (§5.2.7)."""
    # TODO Validate 'sub-types' and 'discriminator' fields
    for required in model.get('required', []):
        if required not in model['properties']:
            raise SwaggerValidationError(
                'Model "%s": required property "%s" not found' %
                (model_name, required))

    for prop_name, prop in model.get('properties', {}).iteritems():
        try:
            validate_data_type(prop, model_ids, allow_refs=True)
        except SwaggerValidationError as e:
            # Add more context to the exception and re-raise
            raise SwaggerValidationError('Model "%s", property "%s": %s' %
                                         (model_name, prop_name, str(e)))
def validate_responses(api, http_verb, responses_dict):
    if is_ref(responses_dict):
        raise SwaggerValidationError(
            '{http_verb} {api} does not have a valid responses section. '
            'That section cannot be just a reference to another object.'.format(
                http_verb=http_verb.upper(),
                api=api,
            )
        )
示例#10
0
def validate_model(model, model_name, model_ids):
    """Validate a Model Object (§5.2.7)."""
    # TODO Validate 'sub-types' and 'discriminator' fields
    for required in model.get('required', []):
        if required not in model['properties']:
            raise SwaggerValidationError(
                'Model "%s": required property "%s" not found' %
                (model_name, required))

    if model_name != model['id']:
        error = 'model name: {} does not match model id: {}'.format(model_name, model['id'])
        raise SwaggerValidationError(error)

    for prop_name, prop in six.iteritems(model.get('properties', {})):
        try:
            validate_data_type(prop, model_ids, allow_refs=True)
        except SwaggerValidationError as e:
            # Add more context to the exception and re-raise
            raise SwaggerValidationError(
                'Model "{}", property "{}": {}'.format(model_name, prop_name, str(e)))
示例#11
0
def test_valid_openapi():
    filename = "openapis/swagger.yaml"
    with codecs.open(filename, encoding="utf-8") as f:
        url = "file://" + filename + "#"
        spec = yaml.safe_load(f)
        if not isinstance(spec, dict):
            raise SwaggerValidationError("root node is not a mapping")
        # ensure the spec is valid JSON
        spec = json.loads(json.dumps(spec))
        validator = swagger_spec_validator.util.get_validator(spec, url)
        validator.validate_spec(spec, url)
def validate_unresolvable_path_params(path_name, path_params):
    """Validate that every path parameter listed is also defined.

    :param path_name: complete path name as a string.
    :param path_params: Names of all the eligible path parameters
    :returns: `None` in case of success, otherwise raises an exception.
    :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
    """
    msg = "Path Parameter used is not defined"
    for path in get_path_params_from_url(path_name):
        if path not in path_params:
            raise SwaggerValidationError("%s: %s" % (msg, path))
def validate_body_parameter(param, deref, def_name):
    if 'schema' not in param:
        raise SwaggerValidationError(
            'Body parameter in `{def_name}` does not specify `schema`.'.format(
                def_name=def_name))

    validate_definition(
        definition=param['schema'],
        deref=deref,
        def_name='{}/schema'.format(def_name),
        visited_definitions_ids=set(),
    )
示例#14
0
def validate_unresolvable_path_params(path_name, path_params):
    """Validate that every path parameter listed is also defined.

    :param path_name: complete path name as a string.
    :param path_params: Names of all the eligible path parameters

    :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
    """
    for path in get_path_params_from_url(path_name):
        if path not in path_params:
            msg = "Path parameter '{}' used is not documented on '{}'".format(path, path_name)
            raise SwaggerValidationError(msg)
def validate_duplicate_param(params):
    """Validate no duplicate parameters are present.
    Uniqueness is determined by the combination of 'name' and 'in'.

    :param params: list of all the params
    :returns: `None` in case of success, otherwise raises an exception.
    :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
    """
    seen = set()
    msg = "Duplicate param found with (name, in)"
    for param in params:
        param_id = (param['name'], param['in'])
        if param_id in seen:
            raise SwaggerValidationError("%s: %s" % (msg, param_id))
        seen.add(param_id)
def validate_definitions(definitions):
    """Validates the semantic errors in `definitions` of the Spec.

    :param apis: dict of all the definitions
    :returns: `None` in case of success, otherwise raises an exception.
    :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
    :raises: :py:class:`jsonschema.exceptions.ValidationError`
    """
    for def_name in definitions:
        definition = definitions[def_name]
        required = definition.get('required', [])
        props = definition.get('properties', {}).keys()
        extra_props = list(set(required) - set(props))
        msg = "Required list has properties not defined"
        if extra_props:
            raise SwaggerValidationError("%s: %s" % (msg, extra_props))
示例#17
0
def validate_definitions(definitions, deref):
    """Validates the semantic errors in #/definitions.

    :param definitions: dict of all the definitions
    :param deref: callable that dereferences $refs

    :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
    :raises: :py:class:`jsonschema.exceptions.ValidationError`
    """
    for def_name, definition in iteritems(definitions):
        definition = deref(definition)
        required = definition.get('required', [])
        props = definition.get('properties', {}).keys()
        extra_props = list(set(required) - set(props))
        if extra_props:
            msg = "Required list has properties not defined"
            raise SwaggerValidationError("%s: %s" % (msg, extra_props))
示例#18
0
def validate_arrays_in_definition(definition_spec,
                                  deref,
                                  def_name=None,
                                  visited_definitions=None):
    if definition_spec.get('type') == 'array':
        if 'items' not in definition_spec:
            raise SwaggerValidationError(
                'Definition of type array must define `items` property{}.'.
                format(
                    '' if not def_name else
                    ' (definition {})'.format(def_name), ), )
        validate_definition(
            definition=definition_spec['items'],
            deref=deref,
            def_name='{}/items'.format(def_name),
            visited_definitions=visited_definitions,
        )
def validate_duplicate_param(params, deref):
    """Validate no duplicate parameters are present.

    Uniqueness is determined by the tuple ('name', 'in').

    :param params: list of all the params
    :param deref: callable that dereferences $refs

    :raises: :py:class:`swagger_spec_validator.SwaggerValidationError` when
        a duplicate parameter is found.
    """
    seen = set()
    msg = "Duplicate param found with (name, in)"
    for param in params:
        param = deref(param)
        param_key = (param['name'], param['in'])
        if param_key in seen:
            raise SwaggerValidationError("%s: %s" % (msg, param_key))
        seen.add(param_key)
示例#20
0
def validate_responses(api, http_verb, responses_dict, deref=None):
    if is_ref(responses_dict):
        raise SwaggerValidationError(
            '{http_verb} {api} does not have a valid responses section. '
            'That section cannot be just a reference to another object.'.format(
                http_verb=http_verb.upper(),
                api=api,
            )
        )
    for response_status, response_spec in iteritems(responses_dict):
        response_schema = response_spec.get('schema')
        if response_schema is None:
            continue
        validate_definition(
            definition=response_schema,
            deref=deref,
            def_name='#/paths/{api}/{http_verb}/responses/{status_code}'.format(
                http_verb=http_verb,
                api=api,
                status_code=response_status,
            ),
            visited_definitions_ids=set(),
        )
def validate_apis(apis, deref):
    """Validates semantic errors in #/paths.

    :param apis: dict of all the #/paths
    :param deref: callable that dereferences $refs

    :raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
    :raises: :py:class:`jsonschema.exceptions.ValidationError`
    """
    operation_tag_to_operation_id_set = defaultdict(set)

    for api_name, api_body in iteritems(apis):
        api_body = deref(api_body)
        api_params = deref(api_body.get('parameters', []))
        validate_duplicate_param(api_params, deref)
        for idx, param in enumerate(api_params):
            validate_parameter(
                param=param,
                deref=deref,
                def_name='#/paths/{api_name}/parameters/{idx}'.format(
                    api_name=api_name,
                    idx=idx,
                ),
            )

        for oper_name in api_body:
            # don't treat parameters that apply to all api operations as
            # an operation
            if oper_name == 'parameters' or oper_name.startswith('x-'):
                continue
            oper_body = deref(api_body[oper_name])
            oper_tags = deref(oper_body.get('tags', [None]))

            # Check that, if this operation has an operationId defined,
            # no other operation with a same tag also has that
            # operationId.
            operation_id = oper_body.get('operationId')
            if operation_id is not None:
                for oper_tag in oper_tags:
                    if operation_id in operation_tag_to_operation_id_set[
                            oper_tag]:
                        raise SwaggerValidationError(
                            "Duplicate operationId: {}".format(operation_id))
                    operation_tag_to_operation_id_set[oper_tag].add(
                        operation_id)

            oper_params = deref(oper_body.get('parameters', []))
            validate_duplicate_param(oper_params, deref)
            all_path_params = list(
                set(
                    get_path_param_names(api_params, deref) +
                    get_path_param_names(oper_params, deref)))
            validate_unresolvable_path_params(api_name, all_path_params)
            for idx, param in enumerate(oper_params):
                validate_parameter(
                    param=param,
                    deref=deref,
                    def_name='#/paths/{api_name}/{oper_name}/parameters/{idx}'.
                    format(
                        api_name=api_name,
                        oper_name=oper_name,
                        idx=idx,
                    ),
                )
            # Responses validation
            validate_responses(api_name, oper_name, oper_body['responses'],
                               deref)
示例#22
0
def _fuzz_parameter(
    parameter: Dict[str, Any],
    operation_id: str = None,
    required: bool = False,
) -> SearchStrategy:
    """
    :param required: for object types, the required parameter is in a
        separate array, rather than being attached to each parameter
        object. This parameter allows objects to pass in this information.
        e.g. {
            'type': 'object',
            'required': [
                'name',
            ],
            'properties': {
                'name': {
                    'type': 'string',
                },
            },
        }
    """
    required = parameter.get('required', required)

    _type = parameter.get('type')
    if not _type:
        raise SwaggerValidationError(
            'Missing \'type\' from {}'.format(
                json.dumps(parameter),
            ),
        )

    strategy = _get_strategy_from_factory(_type, operation_id, parameter.get('name'))

    if not strategy:
        if 'enum' in parameter:
            return st.sampled_from(parameter['enum'])

        # As per https://swagger.io/docs/specification/data-models/data-types,
        # there are only a limited set of data types.
        mapping = {
            'string': _fuzz_string,
            'number': _fuzz_number,
            'integer': _fuzz_integer,
            'boolean': _fuzz_boolean,
            'array': _fuzz_array,
            'object': _fuzz_object,

            # TODO: handle `file` type
            # https://swagger.io/docs/specification/2-0/file-upload/
        }
        fuzz_fn = mapping[_type]
        if fuzz_fn in (_fuzz_object, _fuzz_array):
            strategy = fuzz_fn(parameter, operation_id, required=required)  # type: ignore
        else:
            strategy = fuzz_fn(parameter, required=required)  # type: ignore

    # NOTE: We don't currently support `nullable` values, so we use `None` as a
    #       proxy to exclude the parameter from the final dictionary.
    if (
        # `name` check is used here as a heuristic to determine whether in
        # recursive call (arrays).
        parameter.get('name') and
        not required
    ):
        return st.one_of(st.none(), strategy)  # type: ignore
    return strategy  # type: ignore
示例#23
0
def validate_definition(definition,
                        deref,
                        def_name=None,
                        visited_definitions=None):
    """
    :param visited_definitions: set of already visited definitions
                                    This is used to cut recursion in case of recursive definitions
    :type visited_definitions: set
    """
    if visited_definitions is not None:
        # Remove x-scope or else no two definitions will be the same
        stripped_definition = json.dumps(
            {key: definition[key]
             for key in definition if key != 'x-scope'},
            sort_keys=True)
        if stripped_definition in visited_definitions:
            return
        visited_definitions.add(stripped_definition)

    definition = deref(definition)

    swagger_type = definition.get('type')
    if isinstance(swagger_type, list):
        # not valid Swagger; see https://github.com/OAI/OpenAPI-Specification/issues/458
        raise SwaggerValidationError(
            'In definition of {}, type must be a string; lists are not allowed ({})'
            .format(def_name or '(no name)', swagger_type))

    if 'allOf' in definition:
        for idx, inner_definition in enumerate(definition['allOf']):
            validate_definition(
                definition=inner_definition,
                deref=deref,
                def_name='{}/{}'.format(def_name, str(idx)),
                visited_definitions=visited_definitions,
            )
    else:
        required = definition.get('required', [])
        props = iterkeys(definition.get('properties', {}))
        extra_props = list(set(required) - set(props))
        if extra_props:
            raise SwaggerValidationError(
                "In definition of {}, required list has properties not defined: {}."
                .format(
                    def_name or '(no name)',
                    extra_props,
                ))

        validate_defaults_in_definition(definition, deref)
        validate_arrays_in_definition(definition_spec=definition,
                                      deref=deref,
                                      def_name=def_name,
                                      visited_definitions=visited_definitions)

        for property_name, property_spec in iteritems(
                definition.get('properties', {})):
            validate_definition(
                definition=property_spec,
                deref=deref,
                def_name='{}/properties/{}'.format(def_name, property_name),
                visited_definitions=visited_definitions,
            )

    if 'additionalProperties' in definition:
        if definition.get('additionalProperties') not in (True, False):
            validate_definition(
                definition=definition.get('additionalProperties'),
                deref=deref,
                def_name='{}/additionalProperties'.format(def_name),
                visited_definitions=visited_definitions,
            )

    if 'discriminator' in definition:
        required_props, not_required_props = get_collapsed_properties_type_mappings(
            definition, deref)
        discriminator = definition['discriminator']
        if discriminator not in required_props and discriminator not in not_required_props:
            raise SwaggerValidationError(
                'In definition of {}, discriminator ({}) must be defined in properties'
                .format(def_name or '(no name)', discriminator))
        if discriminator not in required_props:
            raise SwaggerValidationError(
                'In definition of {}, discriminator ({}) must be a required property'
                .format(def_name or '(no name)', discriminator))
        if required_props[discriminator] != 'string':
            raise SwaggerValidationError(
                'In definition of {}, discriminator ({}) must be a string property'
                .format(def_name or '(no name)', discriminator))
示例#24
0
def validate_url(url_string, qualifying=("scheme", "netloc")):
    tokens = urlparse(url_string)
    if not all([getattr(tokens, qual_attr) for qual_attr in qualifying]):
        raise SwaggerValidationError(f"{url_string} invalid")
示例#25
0
def validate_email(email_string):
    d = is_email(email_string, diagnose=True)
    if d > BaseDiagnosis.CATEGORIES["VALID"]:
        raise SwaggerValidationError(f"{email_string} {d.message}")