67 lines
2.5 KiB
Python
67 lines
2.5 KiB
Python
"""Body validation of Needs review decisions and manual links (#249 E3, #536)."""
|
|
|
|
from .annotations import _HEX64, _TOKEN, _UUID
|
|
from .prompts import bad_request
|
|
|
|
UNRESOLVED = "unresolved"
|
|
_GUARD_FIELDS = frozenset(
|
|
{
|
|
"expected_epoch",
|
|
"expected_revision",
|
|
"capture_token",
|
|
"sha256",
|
|
"dependency_fingerprint",
|
|
}
|
|
)
|
|
_DECISION_FIELDS = _GUARD_FIELDS | {"choice"}
|
|
_LINK_FIELDS = _GUARD_FIELDS | {"positive_id", "pair_id"}
|
|
|
|
|
|
def _guard_error(data: dict, fields: frozenset):
|
|
"""The first problem with a decision's shared fields, as a response."""
|
|
if set(data) != fields:
|
|
return bad_request("Request must contain exactly " + ", ".join(sorted(fields)))
|
|
epoch, revision = data["expected_epoch"], data["expected_revision"]
|
|
if not isinstance(epoch, str) or _UUID.fullmatch(epoch) is None:
|
|
return bad_request("expected_epoch must be a canonical UUID")
|
|
if type(revision) is not int or revision < 0:
|
|
return bad_request("expected_revision must be a non-negative integer")
|
|
token = data["capture_token"]
|
|
if token is not None and (
|
|
not isinstance(token, str) or _TOKEN.fullmatch(token) is None
|
|
):
|
|
return bad_request("Invalid capture token")
|
|
for name in ("sha256", "dependency_fingerprint"):
|
|
value = data[name]
|
|
if not isinstance(value, str) or _HEX64.fullmatch(value) is None:
|
|
return bad_request(f"{name} must be 64 lowercase hexadecimal characters")
|
|
return None
|
|
|
|
|
|
def _decision_guards(data: dict):
|
|
"""The validated decision fields, or ``(None, error)``."""
|
|
error = _guard_error(data, _DECISION_FIELDS)
|
|
if error is not None:
|
|
return None, error
|
|
choice = data["choice"]
|
|
if not isinstance(choice, str) or (
|
|
choice != UNRESOLVED and _HEX64.fullmatch(choice) is None
|
|
):
|
|
return None, bad_request("choice must be a candidate hash or unresolved")
|
|
return {**data}, None
|
|
|
|
|
|
def _link_guards(data: dict):
|
|
"""The validated manual link fields, or ``(None, error)``."""
|
|
error = _guard_error(data, _LINK_FIELDS)
|
|
if error is not None:
|
|
return None, error
|
|
if data["capture_token"] is None:
|
|
return None, bad_request("A link needs a capture of the file")
|
|
positive, pair = data["positive_id"], data["pair_id"]
|
|
if type(positive) is not int or positive < 1:
|
|
return None, bad_request("positive_id must be a positive integer")
|
|
if pair is not None and (type(pair) is not int or pair < 1):
|
|
return None, bad_request("pair_id must be a positive integer or null")
|
|
return {**data}, None
|