Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix schema validation prohibiting job execution with empty string input #554

Merged
merged 7 commits into from
Sep 8, 2023
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Changes:
Fixes:
------
- Fix broken `OpenAPI` schema link references to `OGC API - Processes` repository.
- Fix `Job` creation failing when submitting an empty string as input for a `Process` that allows it due
to schema validation incorrectly preventing it.
- Fix ``GET /providers/{provider_id}`` response using ``$schema`` instead of ``$id`` to provide its content schema.

.. _changes_4.30.1:
Expand Down
20 changes: 20 additions & 0 deletions tests/functional/test_wps_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
setup_config_with_celery,
setup_config_with_mongodb,
setup_config_with_pywps,
setup_mongodb_jobstore,
setup_mongodb_processstore
)
from weaver import xml_util
Expand All @@ -45,6 +46,7 @@ def setUp(self):
config = setup_config_with_pywps(config)
config = setup_config_with_celery(config)
self.process_store = setup_mongodb_processstore(config)
self.job_store = setup_mongodb_jobstore(config)
self.app = get_test_weaver_app(config=config, settings=settings)

# add processes by database Process type
Expand Down Expand Up @@ -146,6 +148,24 @@ def test_execute_allowed_demo(self):
resp.mustcontain("<wps:ProcessAccepted")
resp.mustcontain(f"PyWPS Process {HelloWPS.identifier}")

def test_execute_allowed_empty_string(self):
template = "service=wps&request=execute&version=1.0.0&identifier={}&datainputs=name="
params = template.format(HelloWPS.identifier)
url = self.make_url(params)
with contextlib.ExitStack() as stack_exec:
for mock_exec in mocked_execute_celery():
stack_exec.enter_context(mock_exec)
resp = self.app.get(url)
assert resp.status_code == 200 # FIXME: replace by 202 Accepted (?) https://github.com/crim-ca/weaver/issues/14
assert resp.content_type in ContentType.ANY_XML
resp.mustcontain("<wps:ExecuteResponse")
resp.mustcontain("<wps:ProcessAccepted")
resp.mustcontain(f"PyWPS Process {HelloWPS.identifier}")
loc = resp.xml.get("statusLocation")
job_id = loc.rsplit("/", 1)[-1].split(".", 1)[0]
job = self.job_store.fetch_by_id(job_id)
assert job.inputs[0]["data"] == ""

def test_execute_deployed_with_visibility_allowed(self):
headers = {"Accept": ContentType.APP_XML}
params_template = "service=wps&request=execute&version=1.0.0&identifier={}&datainputs=test_input=test"
Expand Down
15 changes: 15 additions & 0 deletions tests/wps_restapi/test_processes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,21 @@ def test_execute_process_no_json_body(self):
assert resp.status_code == 400
assert resp.content_type == ContentType.APP_JSON

def test_execute_process_valid_empty_string(self):
"""
Ensure that a process expecting an input string parameter can be provided as empty (not resolved as "missing").
"""
path = f"/processes/{self.process_public.identifier}/jobs"
data = self.get_process_execute_template(test_input="")

with contextlib.ExitStack() as stack:
for exe in mocked_process_job_runner():
stack.enter_context(exe)
resp = self.app.post_json(path, params=data, headers=self.json_headers)
assert resp.status_code == 201, "Expected job submission without inputs created without error."
job = self.job_store.fetch_by_id(resp.json["jobID"])
assert job.inputs[0]["data"] == "", "Input value should be an empty string."

def test_execute_process_missing_required_params(self):
"""
Validate execution against missing parameters.
Expand Down
6 changes: 3 additions & 3 deletions weaver/processes/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -2861,10 +2861,10 @@ def wps2json_job_payload(wps_request, wps_process):
for input_value in input_list:
input_data = input_value.get("data")
input_href = input_value.get("href")
if input_data:
data["inputs"].append({"id": iid, "data": input_data})
elif input_href:
if input_href: # when href is provided, it must always be non-empty
data["inputs"].append({"id": iid, "href": input_href})
else: # no check if value to allow possible empty string, numeric zero or explicit null
data["inputs"].append({"id": iid, "data": input_data})
output_ids = list(wps_request.outputs)
for output in wps_process.outputs:
oid = output.identifier
Expand Down
2 changes: 1 addition & 1 deletion weaver/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3488,7 +3488,7 @@ def clean_json_text_body(body, remove_newlines=True, remove_indents=True):
"""
# cleanup various escape characters and u'' stings
replaces = [(",\n", ", "), ("\\n", " "), (" \n", " "), ("\n'", "'"), ("\"", "\'"),
("u\'", "\'"), ("u\"", "\'"), ("\'\'", "\'"), ("'. ", ""), ("'. '", ""),
("u\'", "\'"), ("u\"", "\'"), ("'. ", ""), ("'. '", ""),
("}'", "}"), ("'{", "{")]
if remove_indents:
replaces.extend([("\\", " "), (" ", " ")])
Expand Down
38 changes: 25 additions & 13 deletions weaver/wps_restapi/swagger_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1314,8 +1314,6 @@ class BoundingBoxInputType(ExtendedMappingSchema):
supportedCRS = SupportedCRSList()


# FIXME: support byte/binary type (string + format:byte) ?
# https://github.com/opengeospatial/ogcapi-processes/blob/master/openapi/schemas/processes-core/binaryInputValue.yaml
class AnyLiteralType(OneOfKeywordSchema):
"""
Submitted values that correspond to literal data.
Expand All @@ -1326,10 +1324,29 @@ class AnyLiteralType(OneOfKeywordSchema):
- :class:`AnyLiteralDefaultType`
"""
_one_of = [
ExtendedSchemaNode(Float(), description="Literal data type representing a floating point number."),
ExtendedSchemaNode(Integer(), description="Literal data type representing an integer number."),
ExtendedSchemaNode(Boolean(), description="Literal data type representing a boolean flag."),
ExtendedSchemaNode(String(), description="Literal data type representing a generic string."),
ExtendedSchemaNode(
Float(),
title="LiteralDataFloat",
description="Literal data type representing a floating point number.",
),
ExtendedSchemaNode(
Integer(),
title="LiteralDataInteger",
description="Literal data type representing an integer number.",
),
ExtendedSchemaNode(
Boolean(),
title="LiteralDataBoolean",
description="Literal data type representing a boolean flag.",
),
ExtendedSchemaNode(
# FIXME: support byte/binary type (string + format:byte) ?
# https://github.com/opengeospatial/ogcapi-processes/blob/master/openapi/schemas/processes-core/binaryInputValue.yaml
# see if we can use 'encoding' parameter available for below 'String' schema-type to handle this?
String(allow_empty=True), # valid to submit a process with empty string
title="LiteralDataString",
description="Literal data type representing a generic string.",
),
]


Expand Down Expand Up @@ -3490,14 +3507,9 @@ class ExecuteInputFile(AnyOfKeywordSchema):
# https://github.com/opengeospatial/ogcapi-processes/blob/master/openapi/schemas/processes-core/binaryInputValue.yaml
# FIXME: does not support bbox
# https://github.com/opengeospatial/ogcapi-processes/blob/master/openapi/schemas/processes-core/bbox.yaml
class ExecuteInputInlineValue(OneOfKeywordSchema):
class ExecuteInputInlineValue(AnyLiteralType):
title = "ExecuteInputInlineValue"
description = "Execute input value provided inline."
_one_of = [
ExtendedSchemaNode(Float(), title="ExecuteInputValueFloat"),
ExtendedSchemaNode(Integer(), title="ExecuteInputValueInteger"),
ExtendedSchemaNode(Boolean(), title="ExecuteInputValueBoolean"),
ExtendedSchemaNode(String(), title="ExecuteInputValueString"),
]


# https://github.com/opengeospatial/ogcapi-processes/blob/master/openapi/schemas/processes-core/inputValue.yaml
Expand Down
Loading