fix: pin Web Push connections to validated addresses (Closes #1096)
This commit is contained in:
1 parent
a38934b099
commit
ad40cb5fb9
3 files changed
+168
-13
No files matched your search
@@ -7,9 +7,14 @@ from types import SimpleNamespace
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from src import dashboard_auth, gitea_proxy, main
|
||||
from src.push_notifications import PushConfiguration, dispatch_unread_updates
|
||||
from src.push_notifications import (
|
||||
PushConfiguration,
|
||||
dispatch_unread_updates,
|
||||
send_web_push,
|
||||
)
|
||||
from src.push_subscription_store import PushSubscriptionStore
|
||||
from src.push_endpoint_policy import UnsafePushEndpoint
|
||||
|
||||
@@ -692,6 +697,75 @@ async def test_dispatch_removes_an_endpoint_that_rebinds_private_without_contact
|
||||
assert store.is_subscribed("unsafe-device") is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_web_push_transport_dials_the_address_validated_for_the_original_tls_host():
|
||||
endpoint = "https://push.example/device-a"
|
||||
resolved = SimpleNamespace(
|
||||
endpoint=endpoint,
|
||||
hostname="push.example",
|
||||
port=443,
|
||||
addresses=("93.184.216.34",),
|
||||
)
|
||||
resolutions = []
|
||||
connection = {}
|
||||
|
||||
async def resolve_once(value):
|
||||
resolutions.append(value)
|
||||
return resolved
|
||||
|
||||
def inspect_transport(**kwargs):
|
||||
session = kwargs["requests_session"]
|
||||
assert session.trust_env is False
|
||||
request = session.prepare_request(requests.Request("POST", endpoint))
|
||||
adapter = session.get_adapter(endpoint)
|
||||
adapter.add_headers(request)
|
||||
host, tls = adapter.build_connection_pool_key_attributes(request, True)
|
||||
connection.update(host=host, tls=tls, headers=dict(request.headers))
|
||||
|
||||
await send_web_push(
|
||||
{"endpoint": endpoint, "keys": {"p256dh": "key", "auth": "secret"}},
|
||||
"{}",
|
||||
PushConfiguration("public", "private", "mailto:ops@example.com"),
|
||||
endpoint_resolver=resolve_once,
|
||||
webpush_sender=inspect_transport,
|
||||
)
|
||||
|
||||
assert resolutions == [endpoint]
|
||||
assert connection["host"] == {
|
||||
"scheme": "https",
|
||||
"host": "93.184.216.34",
|
||||
"port": 443,
|
||||
}
|
||||
assert connection["tls"]["assert_hostname"] == "push.example"
|
||||
assert connection["tls"]["server_hostname"] == "push.example"
|
||||
assert connection["headers"]["Host"] == "push.example"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_connect_time_rebinding_rejection_removes_only_the_unsafe_device(tmp_path):
|
||||
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
||||
for session_id in ("unsafe-device", "healthy-device"):
|
||||
store.upsert(session_id, {
|
||||
"endpoint": f"https://push.example/{session_id}",
|
||||
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
||||
})
|
||||
sent = []
|
||||
|
||||
async def unread():
|
||||
return {"items": [{"id": 8}]}
|
||||
|
||||
async def send(subscription, _payload):
|
||||
if subscription["endpoint"].endswith("unsafe-device"):
|
||||
raise UnsafePushEndpoint("Endpoint rebound before connection")
|
||||
sent.append(subscription["endpoint"])
|
||||
|
||||
config = PushConfiguration("public", "private", "mailto:ops@example.com")
|
||||
assert await dispatch_unread_updates(store, config, unread, send) == 1
|
||||
assert store.is_subscribed("unsafe-device") is False
|
||||
assert store.is_subscribed("healthy-device") is True
|
||||
assert sent == ["https://push.example/healthy-device"]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_transient_endpoint_failure_does_not_block_healthy_devices(tmp_path):
|
||||
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
||||
|
||||
Reference in new issue
Block a user