Upgrade async GPU analysis and workbench UX
This commit is contained in:
@@ -18,7 +18,7 @@ import socket
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import HTTPRedirectHandler, build_opener, urlopen
|
||||
from urllib.request import HTTPRedirectHandler, build_opener
|
||||
|
||||
from app.core.errors import AppError
|
||||
|
||||
@@ -38,12 +38,30 @@ class _RejectRedirects(HTTPRedirectHandler):
|
||||
return None
|
||||
|
||||
|
||||
class _ValidatedRedirects(HTTPRedirectHandler):
|
||||
"""Validate a redirect target before urllib opens the next connection."""
|
||||
|
||||
def __init__(self, expected_url: str) -> None:
|
||||
super().__init__()
|
||||
self.expected_url = expected_url
|
||||
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl): # noqa: ANN001, D102
|
||||
assert_same_origin_redirect(self.expected_url, newurl)
|
||||
return super().redirect_request(req, fp, code, msg, headers, newurl)
|
||||
|
||||
|
||||
def no_redirect_opener():
|
||||
"""An opener that will not follow a redirect anywhere."""
|
||||
|
||||
return build_opener(_RejectRedirects())
|
||||
|
||||
|
||||
def validated_redirect_opener(expected_url: str):
|
||||
"""An opener that validates each redirect before following it."""
|
||||
|
||||
return build_opener(_ValidatedRedirects(expected_url))
|
||||
|
||||
|
||||
def _reject(code: str, message: str, **details: Any) -> AppError:
|
||||
return AppError(code=code, message=message, details=details or None, status_code=502)
|
||||
|
||||
@@ -88,6 +106,20 @@ def assert_public_http_url(url: str) -> None:
|
||||
host = parsed.hostname
|
||||
if not host:
|
||||
raise _reject("OUTBOUND_URL_NOT_ALLOWED", "Outbound request has no host.", url=url)
|
||||
if parsed.username is not None or parsed.password is not None:
|
||||
raise _reject(
|
||||
"OUTBOUND_URL_NOT_ALLOWED",
|
||||
"Bounded acquisition refuses credentials embedded in an outbound URL.",
|
||||
host=host,
|
||||
)
|
||||
try:
|
||||
parsed.port
|
||||
except ValueError as error:
|
||||
raise _reject(
|
||||
"OUTBOUND_URL_NOT_ALLOWED",
|
||||
"Outbound request contains an invalid port.",
|
||||
host=host,
|
||||
) from error
|
||||
|
||||
literal = host.strip("[]")
|
||||
candidates = [literal] if _looks_like_ip(literal) else _resolved_addresses(host)
|
||||
@@ -133,15 +165,28 @@ def assert_same_origin_redirect(original_url: str, final_url: str) -> None:
|
||||
"The official endpoint redirected from HTTPS to an unprotected scheme.",
|
||||
redirect_scheme=final.scheme,
|
||||
)
|
||||
# This catches embedded credentials, invalid ports and non-public
|
||||
# resolutions before the redirect handler can construct the next request.
|
||||
assert_public_http_url(final_url)
|
||||
original_port = original.port or (443 if original.scheme == "https" else 80)
|
||||
final_port = final.port or (443 if final.scheme == "https" else 80)
|
||||
same_scheme_port = final.scheme == original.scheme and final_port == original_port
|
||||
safe_https_upgrade = original.scheme == "http" and final.scheme == "https" and final_port == 443
|
||||
if not (same_scheme_port or safe_https_upgrade):
|
||||
raise _reject(
|
||||
"OUTBOUND_REDIRECT_NOT_ALLOWED",
|
||||
"The official endpoint redirected to a different network origin.",
|
||||
expected_port=original_port,
|
||||
redirect_port=final_port,
|
||||
)
|
||||
|
||||
|
||||
def guarded_opener(expected_url: str, *, allow_redirect: bool = True) -> Callable[..., Any]:
|
||||
"""An ``urlopen`` replacement that verifies where the response came from.
|
||||
"""An ``urlopen`` replacement that keeps redirects on the expected origin.
|
||||
|
||||
``urlopen`` has already followed the redirect chain by the time it returns,
|
||||
so the check is on ``response.url``: the body is still unread, and raising
|
||||
here means nothing off-origin is ever parsed or persisted.
|
||||
Redirect targets are validated by the handler *before* urllib opens the
|
||||
next connection. The final response URL is checked again as a defensive
|
||||
invariant for injected/custom transports.
|
||||
|
||||
``allow_redirect=False`` refuses any redirect at all, which is what the
|
||||
paged OGC feature readers want: a page URL they built themselves should be
|
||||
@@ -151,7 +196,11 @@ def guarded_opener(expected_url: str, *, allow_redirect: bool = True) -> Callabl
|
||||
|
||||
assert_public_http_url(expected_url)
|
||||
|
||||
default_transport = urlopen if allow_redirect else no_redirect_opener().open
|
||||
default_transport = (
|
||||
validated_redirect_opener(expected_url).open
|
||||
if allow_redirect
|
||||
else no_redirect_opener().open
|
||||
)
|
||||
|
||||
def _open(request: Any, *args: Any, _transport: Callable[..., Any] | None = None, **kwargs: Any) -> Any:
|
||||
response = (_transport or default_transport)(request, *args, **kwargs)
|
||||
|
||||
Reference in New Issue
Block a user