Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
92 lines
4.2 KiB
Python
92 lines
4.2 KiB
Python
"""Captured JSON transport for protected callers; never run against live data to stdout."""
|
|
import json
|
|
import ssl
|
|
import sys
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
from mfa_state import NoRedirect, endpoint, unique_object
|
|
|
|
MAX_BODY = 1024 * 1024
|
|
|
|
|
|
def service_endpoint(value, profile='privacyidea'):
|
|
"""Named destinations; profiles never inherit each other's credential scope."""
|
|
if profile == 'privacyidea':
|
|
return endpoint(value)
|
|
hosts = {'lldap-auth': 'lldap.coulomb.social', 'keycape-discovery': 'kc.coulomb.social'}
|
|
if profile not in hosts:
|
|
raise ValueError()
|
|
if value in ('https://' + hosts[profile], 'https://' + hosts[profile] + ':443'):
|
|
return value
|
|
base = endpoint(value)
|
|
if urllib.parse.urlsplit(base).hostname not in ('127.0.0.1', '::1'):
|
|
raise ValueError()
|
|
return base
|
|
|
|
|
|
def request(url, payload=None, token=None, *, method=None, profile='privacyidea'):
|
|
try:
|
|
parsed = urllib.parse.urlsplit(url)
|
|
base = service_endpoint(urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, '', '', '')), profile)
|
|
if parsed.fragment or not parsed.path.startswith('/') or any(ord(c) < 32 for c in url):
|
|
return 0, None
|
|
verb = method or ('POST' if payload is not None else 'GET')
|
|
if profile != 'privacyidea':
|
|
if token is not None or parsed.query: return 0, None
|
|
if profile == 'lldap-auth':
|
|
if (verb != 'POST' or parsed.path != '/auth/simple/login' or not isinstance(payload, dict)
|
|
or set(payload) != {'username', 'password'} or payload['username'] != 'admin'
|
|
or not isinstance(payload['password'], str) or not payload['password']): return 0, None
|
|
elif verb != 'GET' or parsed.path != '/.well-known/openid-configuration' or payload is not None:
|
|
return 0, None
|
|
# Origin validated before creating a request containing protected values.
|
|
headers = {'Accept': 'application/json'}
|
|
data = None
|
|
if payload is not None:
|
|
data = json.dumps(payload).encode('utf-8')
|
|
if len(data) > MAX_BODY: return 0, None
|
|
headers['Content-Type'] = 'application/json'
|
|
if token:
|
|
if not isinstance(token, str) or len(token) > 16384 or any(ord(c) < 33 or ord(c) > 126 for c in token):
|
|
return 0, None
|
|
headers['Authorization'] = token
|
|
req = urllib.request.Request(base + parsed.path + ('?' + parsed.query if parsed.query else ''),
|
|
data=data, headers=headers, method=method or ('POST' if payload is not None else 'GET'))
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
context.load_verify_locations(cafile=ssl.get_default_verify_paths().openssl_cafile)
|
|
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect(),
|
|
urllib.request.HTTPSHandler(context=context))
|
|
with opener.open(req, timeout=20) as response:
|
|
status, raw = response.status, response.read(MAX_BODY + 1)
|
|
if len(raw) > MAX_BODY: return status, None
|
|
return status, json.loads(raw, object_pairs_hook=unique_object) if raw else None
|
|
except urllib.error.HTTPError as exc:
|
|
exc.close()
|
|
return exc.code, None
|
|
except Exception:
|
|
# Callers emit fixed phases, never exception strings or response bodies.
|
|
return 0, None
|
|
|
|
|
|
def main():
|
|
try:
|
|
if len(sys.argv) != 3 or sys.argv[1] not in ('GET', 'POST'):
|
|
raise ValueError()
|
|
token = sys.stdin.readline(16386).removesuffix('\n')
|
|
body = sys.stdin.read(MAX_BODY + 1)
|
|
if len(body) > MAX_BODY: raise ValueError()
|
|
status, result = request(sys.argv[2], json.loads(body, object_pairs_hook=unique_object) if body else None,
|
|
token or None, method=sys.argv[1])
|
|
if not 200 <= status < 300 or not isinstance(result, dict): raise ValueError()
|
|
# Private shell adapter: caller must capture this; it can contain a JWT/config.
|
|
print(json.dumps(result))
|
|
return 0
|
|
except Exception:
|
|
print('CURL_FAILED')
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|