From 390c46cdf37be0aa7a17e0aec98ee7cb4c742d7c Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Thu, 5 Nov 2026 22:09:55 +0000 Subject: [PATCH 1/2] Allow HTTP providers on test domains --- PARITY.md | 6 ++++-- README.md | 2 ++ authwave/uri.py | 4 ++++ tests/test_test_domains.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/test_test_domains.py diff --git a/PARITY.md b/PARITY.md index 37ef561..5072ffa 100644 --- a/PARITY.md +++ b/PARITY.md @@ -1,6 +1,6 @@ # PHP client parity -This Python client implements the current PHP identity contract (`id` and `email`), following PHP commit `bcb547d`. The language API uses snake_case methods, frozen dataclass user snapshots and request-scoped mappings. +This Python client implements the current PHP identity contract (`id` and `email`), following PHP commit `b35909368b3bd78bb3c7401c694aef02d6a560dd`. The language API uses snake_case methods, frozen dataclass user snapshots and request-scoped mappings. | PHP source | Python equivalent | | --- | --- | @@ -35,7 +35,7 @@ This Python client implements the current PHP identity contract (`id` and `email - Logout removes only authentication state before redirect. Explicit destinations override the configured return URI, then the provider default. Query-only destinations replace the current query. - Fake login stores synthetic data, appends its own response query and defers refreshing the current user until the redirected request. Only the default fake email takes the local logout branch. - The low-level token decoder uses the request IV and accepts `uuid` or `id`; the normal response path uses the secret session IV and `id`. Extra fields are ignored, matching the current PHP client. -- Administration links point to `/admin/` and do not grant access. HTTPS is required except for `localhost` and the literal `127.0.0.127`. +- Administration links point to `/admin/` and do not grant access. HTTPS is required except for `localhost`, the literal `127.0.0.127`, and HTTP providers with a `.test` hostname suffix. - The unused legacy session-store constant and deprecated user alias/getter are retained. Actual storage uses `Authwave\SessionData`. ## Runtime adaptations @@ -47,3 +47,5 @@ Redirects transfer control to adapters, which save before sending a 303. Applica PHP dependency APIs such as the entire PSR-7 URI interface are not reimplemented; native language URI types/helpers provide the operations needed by the client. PHP's diagnostic logging and warning mechanisms use the host language's conventions. Browser login against a deployed provider is separate from the automated simulated-provider tests. Python emits `DeprecationWarning` for `get_user()`. Default redirects raise `RedirectSignal`. Optional Flask and Django decorators and ASGI/WSGI middleware buffer responses and use opaque cookies. FastAPI uses the ASGI middleware. Server-side store methods are synchronous; ASGI executes store operations and constructor callbacks in worker threads. + +Provider HTTP policy also allows the `.test` hostname suffix (case-insensitive, ignoring trailing DNS dots), for local test providers. The exception does not allow other schemes. diff --git a/README.md b/README.md index 68b995f..d180d71 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,5 @@ python -m build Tests include 68 deterministic PHP-reference scenarios and complete framework login/logout flows using simulated provider responses. To also execute the PHP source and check the fixtures, set `AUTHWAVE_PHP_REFERENCE` to the PHP checkout with installed Composer dependencies. [PARITY.md](PARITY.md) maps the PHP API to its Python equivalents. + +For a local test provider, HTTP is supported for hosts ending in `.test`, such as `http://login.realtest.authwave.test:9090`. Configure local DNS or a hosts-file entry for the provider. This exception applies only to HTTP and the complete `.test` hostname suffix; `.authwave-test` and names such as `example.test.com` do not qualify. Other remote providers require HTTPS. Use test accounts on a trusted development network; the suffix does not encrypt traffic or guarantee that it stays on your machine. diff --git a/authwave/uri.py b/authwave/uri.py index 0283334..5608ca8 100644 --- a/authwave/uri.py +++ b/authwave/uri.py @@ -59,8 +59,12 @@ def normalize_base_uri(base: str) -> str: parts = urlsplit(base if "://" in base else "https://" + base) if not parts.hostname: raise ValueError("Provider hostname is required") + is_test_http = parts.scheme == "http" and parts.hostname.lower().rstrip( + "." + ).endswith(".test") if ( parts.hostname not in ("localhost", "127.0.0.127") + and not is_test_http and parts.scheme != "https" ): raise InsecureProtocolException(parts.scheme) diff --git a/tests/test_test_domains.py b/tests/test_test_domains.py new file mode 100644 index 0000000..a1fbee0 --- /dev/null +++ b/tests/test_test_domains.py @@ -0,0 +1,33 @@ +import pytest +from authwave import AdminUri, InsecureProtocolException + + +@pytest.mark.parametrize( + "host,allowed", + [ + ("http://login.realtest.authwave.test:9090", True), + ("http://app.test", True), + ("http://LOGIN.EXAMPLE.TEST:9090", True), + ("http://login.example.test.:9090", True), + ("http://localhost:9090", True), + ("http://127.0.0.127:9090", True), + ("https://login.authwave.com", True), + ("http://login.realtest.authwave-test:9090", False), + ("http://example.test.attacker.com", False), + ("http://exampletest", False), + ("http://test", False), + ("http://login.authwave.com", False), + ("ftp://example.test", False), + ], +) +def test_provider_protocol_policy(host, allowed): + if not allowed: + with pytest.raises(InsecureProtocolException): + AdminUri(host) + return + uri = AdminUri(host) + assert uri.get_path() == "/admin/" + if host.startswith("http://"): + assert uri.get_scheme() == "http" + if ":9090" in host: + assert uri.get_port() == 9090 From eef00683eb80db5aa90d5afb6a4724d9e72686b1 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Thu, 5 Nov 2026 22:12:10 +0000 Subject: [PATCH 2/2] Use the verified PHP reference revision --- PARITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PARITY.md b/PARITY.md index 5072ffa..ce5800c 100644 --- a/PARITY.md +++ b/PARITY.md @@ -1,6 +1,6 @@ # PHP client parity -This Python client implements the current PHP identity contract (`id` and `email`), following PHP commit `b35909368b3bd78bb3c7401c694aef02d6a560dd`. The language API uses snake_case methods, frozen dataclass user snapshots and request-scoped mappings. +This Python client implements the current PHP identity contract (`id` and `email`), following PHP commit `4dcc01fe5d07933400c82695bf9574097ee19d43`. The language API uses snake_case methods, frozen dataclass user snapshots and request-scoped mappings. | PHP source | Python equivalent | | --- | --- |