Constrain authenticated HTTP origins and verify realization surfaces
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e76f-be98-7ae3-965d-e0b31290a4c4
This commit is contained in:
parent
0f8559f442
commit
e419bfe029
9 changed files with 273 additions and 8 deletions
|
|
@ -39,9 +39,45 @@ TARGET = '/resources/R/grant'
|
|||
FIELDS = {'permission': 'READ', 'subject_id': 'bob'}
|
||||
|
||||
|
||||
class OriginViolation(ValueError):
|
||||
"""An authenticated request attempted to leave its configured origin."""
|
||||
|
||||
|
||||
def _origin(url):
|
||||
try:
|
||||
parsed = urllib.parse.urlsplit(url)
|
||||
if (parsed.scheme not in ('http', 'https') or not parsed.hostname
|
||||
or parsed.username is not None or parsed.password is not None):
|
||||
raise ValueError
|
||||
return (parsed.scheme, parsed.hostname,
|
||||
parsed.port if parsed.port is not None else (443 if parsed.scheme == 'https' else 80))
|
||||
except ValueError:
|
||||
raise OriginViolation('invalid authenticated HTTP origin') from None
|
||||
|
||||
|
||||
class _OriginRedirectHandler(urllib.request.HTTPRedirectHandler):
|
||||
def __init__(self, origin):
|
||||
self.origin = origin
|
||||
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
if _origin(newurl) != self.origin:
|
||||
raise OriginViolation('authenticated redirect leaves configured origin')
|
||||
return super().redirect_request(req, fp, code, msg, headers, newurl)
|
||||
|
||||
|
||||
def authenticated_target(base_url, path):
|
||||
origin = _origin(base_url)
|
||||
target = urllib.parse.urljoin(base_url, path)
|
||||
if _origin(target) != origin:
|
||||
raise OriginViolation('authenticated request leaves configured origin')
|
||||
# The caller supplies the authorization header only after target validation.
|
||||
return target, urllib.request.build_opener(_OriginRedirectHandler(origin))
|
||||
|
||||
|
||||
def _post(base_url: str, token: str, path: str, fields: dict) -> int:
|
||||
target, opener = authenticated_target(base_url, path)
|
||||
request = urllib.request.Request(
|
||||
urllib.parse.urljoin(base_url, path),
|
||||
target,
|
||||
data=urllib.parse.urlencode(fields).encode(),
|
||||
method="POST",
|
||||
headers={
|
||||
|
|
@ -50,7 +86,7 @@ def _post(base_url: str, token: str, path: str, fields: dict) -> int:
|
|||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=10) as response:
|
||||
with opener.open(request, timeout=10) as response:
|
||||
return response.status
|
||||
except urllib.error.HTTPError as error:
|
||||
return error.code
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue