Fix 403 on test-woo: add User-Agent header, classify 403 as WooForbiddenError

WordPress security plugins (Wordfence, WAF) block the default
Python-urllib User-Agent with 403. Send a descriptive User-Agent on
all Woo requests. Also add WooForbiddenError so 403 is classified
distinctly from 401 (auth) per SETTINGS-80, and surface a clear
message in test-woo.
This commit is contained in:
2026-09-20 07:59:00 -07:00
parent d1e862770d
commit b99cbdf194
3 changed files with 42 additions and 1 deletions
+16 -1
View File
@@ -13,6 +13,10 @@ from typing import Any, Callable
from .crypto import DecryptError, decrypt
# WordPress security plugins (Wordfence, WAF, CDN) commonly block the default
# Python-urllib User-Agent with 403. Send a descriptive one.
_USER_AGENT = "SlabUploader/1.0 (+https://www.whidbeywoodstore.com)"
class WooError(Exception):
"""Base for all Woo client failures."""
@@ -26,6 +30,10 @@ class WooAuthError(WooError):
"""The store rejected the credentials (401)."""
class WooForbiddenError(WooError):
"""The store blocked the request (403) — usually a security plugin or WAF."""
class WooNotFoundError(WooError):
pass
@@ -54,6 +62,8 @@ def _default_request(url: str, method: str, data: bytes | None, headers: dict[st
msg = parsed.get("message") if isinstance(parsed, dict) else None
if e.code == 401:
raise WooAuthError(msg or "authentication rejected (401)") from e
if e.code == 403:
raise WooForbiddenError(msg or "request forbidden (403)") from e
if e.code == 404:
raise WooNotFoundError(msg or "not found (404)") from e
raise WooServerError(f"server error {e.code}: {msg}") from e
@@ -90,7 +100,11 @@ class WooClient:
url = f"{self.base_url}/wp-json/wc/v3{path}"
return self._request(
url, "GET", None,
{"Authorization": f"Basic {self._auth}", "Accept": "application/json"},
{
"Authorization": f"Basic {self._auth}",
"Accept": "application/json",
"User-Agent": _USER_AGENT,
},
)
def _fetch_all(self, path: str) -> list[dict[str, Any]]:
@@ -132,6 +146,7 @@ class WooClient:
{
"Authorization": f"Basic {self._auth}",
"Content-Type": f"multipart/form-data; boundary={boundary}",
"User-Agent": _USER_AGENT,
},
)
if code not in (200, 201):