Skip to content

Commit 3bb88ad

Browse files
committed
feat: Add safe query params hook
1 parent 3a456a0 commit 3bb88ad

1 file changed

Lines changed: 53 additions & 24 deletions

File tree

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,57 @@
1-
from cloudharness.auth import get_token
21
import os
32
import logging
4-
53
import schemathesis as st
4+
from schemathesis.hooks import HookContext
5+
6+
from cloudharness.auth import get_token
67
st.experimental.OPEN_API_3_1.enable()
78

89

10+
import os
11+
import logging
12+
import schemathesis as st
13+
14+
# Enable experimental OpenAPI 3.1 support if needed
15+
st.experimental.OPEN_API_3_1.enable()
16+
917
if "APP_URL" or "APP_SCHEMA_FILE" in os.environ:
1018
app_schema = os.environ.get("APP_SCHEMA_FILE", None)
1119
app_url = os.environ.get("APP_URL", "http://samples.ch.local/api")
1220
logging.info("Start schemathesis tests on %s", app_url)
21+
1322
schema = None
23+
24+
# First, attempt to load the local file if provided
1425
if app_schema:
15-
# Test locally with harness-test -- use local schema for convenience during test development
16-
openapi_uri = app_schema
1726
try:
18-
schema = st.from_file(openapi_uri)
27+
schema = st.from_file(app_schema)
28+
logging.info("Successfully loaded schema from local file: %s", app_schema)
1929
except st.exceptions.SchemaError:
20-
logging.exception("The local schema file %s cannot be loaded. Attempting loading from URL", openapi_uri)
30+
logging.exception("The local schema file %s cannot be loaded. Attempting loading from URL", app_schema)
2131

32+
# If no schema from file, then loop over URL candidates
2233
if not schema:
23-
# Try app_url/openapi.json
24-
try:
25-
openapi_uri = app_url.rstrip("/") + "/openapi.json"
26-
logging.info("Using openapi spec at %s", openapi_uri)
27-
schema = st.from_uri(openapi_uri)
28-
except st.exceptions.SchemaError:
29-
logging.warning("Failed to load schema from %s", openapi_uri)
30-
31-
# Then try app_url/api/openapi.json
34+
candidates = [
35+
app_url.rstrip("/") + "/openapi.json",
36+
app_url.rstrip("/") + "/api/openapi.json",
37+
]
38+
for candidate in candidates:
3239
try:
33-
openapi_uri = app_url.rstrip("/") + "/api/openapi.json"
34-
logging.info("Using openapi spec at %s", openapi_uri)
35-
schema = st.from_uri(openapi_uri)
40+
logging.info("Attempting to load schema from URI: %s", candidate)
41+
schema = st.from_uri(candidate)
42+
logging.info("Successfully loaded schema from %s", candidate)
43+
break # Exit loop on successful load
3644
except st.exceptions.SchemaError as e:
37-
raise Exception(
38-
f"Cannot setup api tests: {openapi_uri} not valid. Check your deployment is up and configuration") from e
45+
logging.warning("Failed to load schema from %s: %s", candidate, e)
46+
except Exception as e:
47+
logging.error("Unexpected error when loading schema from %s: %s", candidate, e)
48+
if not schema:
49+
raise Exception("Cannot setup API tests: No valid schema found. Check your deployment and configuration.")
3950

40-
except Exception as e:
41-
raise Exception(
42-
f"Cannot setup api tests: {openapi_uri}: {e}") from e
4351

4452
if "USERNAME" in os.environ and "PASSWORD" in os.environ:
4553
logging.info("Setting token from username and password")
46-
54+
4755
@st.auth.register()
4856
class TokenAuth:
4957
def get(self, context):
@@ -68,3 +76,24 @@ def set(self, case, data, context):
6876
case.headers = case.headers or {}
6977
case.headers["Authorization"] = f"Bearer {data}"
7078
case.headers["Cookie"] = f"kc-access={data}"
79+
80+
UNSAFE_VALUES = ("%")
81+
82+
@st.hook
83+
def before_generate_path_parameters(context: HookContext, strategy):
84+
def valid_param(x):
85+
# Extract the candidate value.
86+
param = x["key"] if isinstance(x, dict) and "key" in x else x
87+
88+
if param is None or param == "":
89+
return True
90+
91+
param_str = str(param)
92+
93+
# Reject if any unsafe substring is present.
94+
if any(unsafe in param_str for unsafe in UNSAFE_VALUES):
95+
return False
96+
97+
return True
98+
99+
return strategy.filter(valid_param)

0 commit comments

Comments
 (0)