proto(woo): blocking setup gate; require X-Woo-* on Woo HTTP routes
Full-screen WooSetupGate on App mount when this browser lacks device WP user + app password. Save + Test Woo dismisses only after a successful test. Publish/test-woo auth failures reopen the gate. Server: client_for_request requires X-Woo-* on publish, taxonomy sync, and test-woo (401 woo_device_creds_required); health probe keeps Settings. Optional SLAB_WOO_ALLOW_SETTINGS_FALLBACK for legacy tests. See PROTOTYPE.md.
This commit is contained in:
+38
-12
@@ -1,12 +1,11 @@
|
||||
"""WooCommerce REST v3 client (stdlib urllib). Basic-auth, HTTPS-only, paginated.
|
||||
|
||||
Default creds come from Settings (wp_username plain, app_password AES-GCM).
|
||||
Missing creds raise WooCredentialsMissing (fail-closed), distinct from
|
||||
WooAuthError (401 from the store).
|
||||
Shop-default creds live in Settings (wp_username plain, app_password AES-GCM) and
|
||||
are used by the health probe only. HTTP Woo routes (publish / taxonomy sync /
|
||||
test-woo) require X-Woo-Username + X-Woo-App-Password on the request — no
|
||||
Settings fallback. Missing headers raise WooDeviceCredsRequired. See PROTOTYPE.md.
|
||||
|
||||
Prototype per-user override: callers may pass X-Woo-Username + X-Woo-App-Password
|
||||
request headers into client_for_request(); those build a per-request WooClient
|
||||
and are never written into Settings. See PROTOTYPE.md.
|
||||
Optional escape hatch for legacy tests: SLAB_WOO_ALLOW_SETTINGS_FALLBACK=1.
|
||||
"""
|
||||
|
||||
import base64
|
||||
@@ -30,6 +29,10 @@ class WooCredentialsMissing(WooError):
|
||||
"""Woo base URL or credentials are not configured (fail-closed)."""
|
||||
|
||||
|
||||
class WooDeviceCredsRequired(WooCredentialsMissing):
|
||||
"""X-Woo-Username + X-Woo-App-Password required; Settings fallback disabled."""
|
||||
|
||||
|
||||
class WooAuthError(WooError):
|
||||
"""The store rejected the credentials (401)."""
|
||||
|
||||
@@ -188,9 +191,9 @@ class WooClient:
|
||||
# Woo-touching API calls via:
|
||||
# X-Woo-Username
|
||||
# X-Woo-App-Password
|
||||
# Server uses those for THIS request's WooClient only; never writes them into
|
||||
# the Settings row. If either header is missing/blank, fall back to the
|
||||
# shop-default singleton (woo_wp_username + encrypted woo_app_password).
|
||||
# HTTP Woo routes REQUIRE both headers (no Settings fallback). Health probe
|
||||
# still uses shop-default Settings via WooClient.from_settings / settings_woo_creds.
|
||||
# Never write override headers into the Settings row.
|
||||
|
||||
HEADER_WOO_USERNAME = "X-Woo-Username"
|
||||
HEADER_WOO_APP_PASSWORD = "X-Woo-App-Password"
|
||||
@@ -218,10 +221,29 @@ def request_woo_override(headers) -> tuple[str, str] | None:
|
||||
return None
|
||||
|
||||
|
||||
def client_for_request(conn, headers=None, request: Callable[..., tuple[int, dict, bytes]] | None = None) -> WooClient:
|
||||
"""Build a WooClient for one HTTP request: override headers → Settings fallback.
|
||||
def _allow_settings_fallback() -> bool:
|
||||
"""Legacy-test escape hatch. Production HTTP Woo routes leave this unset."""
|
||||
import os
|
||||
|
||||
Never mutates Settings. Inference stays on shared Settings (caller responsibility).
|
||||
return os.environ.get("SLAB_WOO_ALLOW_SETTINGS_FALLBACK", "").strip().lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
)
|
||||
|
||||
|
||||
def client_for_request(
|
||||
conn,
|
||||
headers=None,
|
||||
request: Callable[..., tuple[int, dict, bytes]] | None = None,
|
||||
*,
|
||||
require_override: bool = True,
|
||||
) -> WooClient:
|
||||
"""Build a WooClient for one HTTP Woo request.
|
||||
|
||||
Requires X-Woo-* headers by default (no Settings fallback). Set
|
||||
require_override=False or SLAB_WOO_ALLOW_SETTINGS_FALLBACK=1 only for
|
||||
health-adjacent / legacy-test paths. Never mutates Settings.
|
||||
"""
|
||||
base, settings_user, settings_enc = settings_woo_creds(conn)
|
||||
override = request_woo_override(headers)
|
||||
@@ -230,4 +252,8 @@ def client_for_request(conn, headers=None, request: Callable[..., tuple[int, dic
|
||||
if request is None:
|
||||
request = _default_request
|
||||
return WooClient(user, password, base, request)
|
||||
if require_override and not _allow_settings_fallback():
|
||||
raise WooDeviceCredsRequired(
|
||||
"X-Woo-Username and X-Woo-App-Password are required on this device"
|
||||
)
|
||||
return WooClient.from_settings(base, settings_user, settings_enc, request=request)
|
||||
|
||||
Reference in New Issue
Block a user