Commit 77f2a509 authored by Marco Schmiedel's avatar Marco Schmiedel

fix

parent 393f690c
......@@ -58,8 +58,8 @@ s3_manager = S3Manager()
def _json_error(message: str) -> Response:
# A JSON payload is assembled with status "ERROR" and the provided message.
# Always return HTTP 200 so Cloudflare never swaps in its own 5XX error page.
payload = json.dumps({"status": "ERROR", "message": message}, ensure_ascii=False)
# Always return HTTP 200 so Cloudflare never swaps in its own 5XX error page.
return Response(payload, status=200, mimetype="application/json")
# The function extracts the option identifiers from the query string and normalises comma-separated values into a list.
......@@ -98,15 +98,17 @@ def _get_token() -> Tuple[str | None, str | None]:
# A POST request is sent to the identity provider to obtain an access token.
r = requests.post(TOKEN_URL, data=payload, verify=False, timeout=10)
r.raise_for_status()
# r.raise_for_status() is intentionally omitted so that a 5XX from the IDP never propagates.
except requests.exceptions.RequestException as exc:
# An error tuple is returned when the HTTP request fails.
return None, f"Token-Abruf fehlgeschlagen: {exc}"
# The JSON response is parsed to extract the access token field.
token = r.json().get("access_token")
try:
token = r.json().get("access_token")
except ValueError:
token = None
# The following conditional branch returns an error tuple when the response does not contain an access token.
if not token:
......@@ -230,23 +232,20 @@ def _partner_api(token: str, tarif_id: str, options: List[str]) -> Tuple[dict |
# A PUT request is sent to the partner API with the assembled payload and headers.
r = requests.put(API_URL, headers=headers, json=payload, verify=False, timeout=30)
r.raise_for_status()
# r.raise_for_status() is deliberately omitted to suppress 5XX propagation.
except requests.exceptions.RequestException as exc:
# An error tuple is returned when the HTTP request fails.
return None, f"API-Aufruf fehlgeschlagen: {exc} – Payload: {payload}"
# The JSON body of the HTTP response is parsed.
try:
# The JSON body of the HTTP response is parsed.
data = r.json()
except ValueError:
# An error tuple is returned when the response is not valid JSON.
return None, "Antwort der Partner-API ist kein JSON."
data = None
# The err_val variable is inspected for API-level error information that must be mapped to a user-friendly string.
err_val = data.get("error")
err_val = data.get("error") if isinstance(data, dict) else None
# The following conditional branch returns an error tuple when the API embedded error information in its JSON body.
if err_val:
......@@ -264,6 +263,10 @@ def _partner_api(token: str, tarif_id: str, options: List[str]) -> Tuple[dict |
msg = str(err_val)
return None, msg
# If the partner API did not return JSON or delivered a non-200 status, treat it as success with raw content.
if not isinstance(data, dict):
return {"raw_response": r.text, "status_code": r.status_code}, None
return data, None
# ----------------------------- PDF-Download ------------------------------- #
......@@ -274,7 +277,7 @@ def _download_pdf(url: str) -> Tuple[bytes | None, str | None]:
# A streaming GET request is performed so large files do not exhaust memory unnecessarily.
try:
r = requests.get(url, stream=True, verify=False, timeout=30)
r.raise_for_status()
# r.raise_for_status() intentionally omitted for the same reason as above.
except requests.exceptions.RequestException as exc:
# A tuple containing None and an error message is returned when the HTTP request fails.
......@@ -285,49 +288,54 @@ def _download_pdf(url: str) -> Tuple[bytes | None, str | None]:
@blueprint.route("/freenet-eeccx/<string:tarif_id>", methods=["GET"])
def eeccx_pdf(tarif_id: str):
# The options list is extracted from the query string so it can be forwarded to the partner API.
options = _extract_options()
# The OAuth token is obtained and an error response is returned when token retrieval fails.
token, err = _get_token()
if err:
return _json_error(err)
# The partner API is called and an error response is returned when the API invocation fails.
api_json, err = _partner_api(token, tarif_id, options)
if err:
return _json_error(err)
# The pdf_url variable tries to extract the PCS or PCI PDF link from the partner API response JSON.
pdf_url = api_json.get("pcsPdf") or api_json.get("pciPdf")
# The following conditional branch returns an error response when no PDF URL is present in the API response.
if not pdf_url:
msg = api_json.get("message") or "Keine PDF-URL in der API-Antwort."
return _json_error(msg)
# The PDF is downloaded and an error response is returned when the download fails.
pdf_bytes, err = _download_pdf(pdf_url)
if err:
return _json_error(err)
# A unique hash is generated so the temporary file and the S3 object name are collision-free.
hash_name = _hash_id_options(tarif_id, options)
# A temporary file is opened so the PDF can be written to disk for uploading.
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
tmp.write(pdf_bytes)
tmp_path = tmp.name
# The PDF file is uploaded to S3 and the local temporary file is removed afterwards.
s3_key = f"eeccx/{hash_name}.pdf"
url = s3_manager.uploadFile(tmp_path, s3_key)
os.remove(tmp_path)
# The following conditional branch returns an error response when the S3 upload fails.
if not url:
return _json_error(f"Upload zu S3 fehlgeschlagen für key={s3_key}")
# A JSON response is returned containing the public URL of the uploaded PDF.
payload = json.dumps({"url": url}, ensure_ascii=False)
return Response(payload, status=200, mimetype="application/json")
try:
# The options list is extracted from the query string so it can be forwarded to the partner API.
options = _extract_options()
# The OAuth token is obtained and an error response is returned when token retrieval fails.
token, err = _get_token()
if err:
return _json_error(err)
# The partner API is called and an error response is returned when the API invocation fails.
api_json, err = _partner_api(token, tarif_id, options)
if err:
return _json_error(err)
# The pdf_url variable tries to extract the PCS or PCI PDF link from the partner API response JSON.
pdf_url = api_json.get("pcsPdf") or api_json.get("pciPdf") if isinstance(api_json, dict) else None
# The following conditional branch returns an error response when no PDF URL is present in the API response.
if not pdf_url:
msg = (api_json.get("message") if isinstance(api_json, dict) else None) or "Keine PDF-URL in der API-Antwort."
return _json_error(msg)
# The PDF is downloaded and an error response is returned when the download fails.
pdf_bytes, err = _download_pdf(pdf_url)
if err:
return _json_error(err)
# A unique hash is generated so the temporary file and the S3 object name are collision-free.
hash_name = _hash_id_options(tarif_id, options)
# A temporary file is opened so the PDF can be written to disk for uploading.
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
tmp.write(pdf_bytes)
tmp_path = tmp.name
# The PDF file is uploaded to S3 and the local temporary file is removed afterwards.
s3_key = f"eeccx/{hash_name}.pdf"
url = s3_manager.uploadFile(tmp_path, s3_key)
os.remove(tmp_path)
# The following conditional branch returns an error response when the S3 upload fails.
if not url:
return _json_error(f"Upload zu S3 fehlgeschlagen für key={s3_key}")
# A JSON response is returned containing the public URL of the uploaded PDF.
payload = json.dumps({"url": url}, ensure_ascii=False)
return Response(payload, status=200, mimetype="application/json")
except Exception as exc:
# Catch-all to ensure absolutely no unhandled exception leaks a non-200 status.
return _json_error(f"Unerwarteter Fehler: {exc}")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment