#!/usr/bin/env python3 """Black-box shared-token acceptance against the loopback-only fixture launcher.""" import argparse import http.cookiejar import ipaddress import json from pathlib import Path import time import urllib.error import urllib.parse import urllib.request class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("fixture_config", type=Path) args = parser.parse_args() cfg = json.loads(args.fixture_config.read_text()) origins = (cfg["publicOrigin"], cfg["baseURL"]) for value in origins: parsed = urllib.parse.urlsplit(value) assert parsed.scheme == "http" and ipaddress.ip_address(parsed.hostname).is_loopback assert not parsed.username and not parsed.query and not parsed.fragment and not parsed.path platform, upstream = origins jar = http.cookiejar.CookieJar() opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), urllib.request.HTTPCookieProcessor(jar), NoRedirect()) def request(origin, path, body=None, credential=None, headers=None, form=False, method=None): assert origin in origins and path.startswith("/") and not path.startswith("//") h = {"Origin": platform} if credential: h.update({"Authorization": "Bearer " + credential["accessToken"], "X-OneOS-Client-ID": credential["clientId"]}) if headers: h.update(headers) data = None if body is not None: h["Content-Type"] = "application/x-www-form-urlencoded" if form else "application/json" data = (urllib.parse.urlencode(body) if form else json.dumps(body)).encode() req = urllib.request.Request(origin + path, data=data, headers=h, method=method) try: resp = opener.open(req, timeout=5) except urllib.error.HTTPError as error: resp = error with resp: raw = resp.read() result = json.loads(raw) if "json" in resp.headers.get("Content-Type", "") else None return resp.code, result, resp.headers passed = [] def check(name, condition): assert condition, name passed.append(name) print("PASS " + name, flush=True) def login(name): status, body, _ = request(platform, "/api/v2/auth/oneos/login", {"username": name, "password": "LocalOnly123!"}) assert status == 200, "local fixture login failed" return body["data"] def control(name, action): status, _, _ = request(upstream, "/__local/control", {"username": name, "action": action}, headers={"Authorization": "Bearer " + cfg["serviceToken"]}) assert status == 200 alpha, beta = login("alpha"), login("beta") status, identity, _ = request(upstream, "/system/user/getInfo", headers={"Authorization": "Bearer " + alpha["accessToken"], "ClientID": alpha["clientId"]}) check("platform-returned token works unchanged on OneOS", status == 200 and identity["data"]["user"]["userName"] == "alpha") status, me, _ = request(platform, "/api/v2/auth/oneos/me", credential=alpha) check("platform invokes live OneOS as current user", status == 200 and me["data"]["username"] == "alpha") _, a, _ = request(platform, "/api/vehicles", credential=alpha) _, b, _ = request(platform, "/api/vehicles", credential=beta) check("two user vehicle lists remain disjoint", {v["vin"] for v in a["data"]["items"]} == {"LB9A32A24R0LS1426"} and {v["vin"] for v in b["data"]["items"]} == {"LNXNEGRR7SR318212"}) status, _, _ = request(platform, "/api/v2/tracks?vin=LNXNEGRR7SR318212", credential=alpha) check("explicit cross-user VIN rejected", status == 403) status, _, _ = request(platform, "/api/v2/admin/users", credential=alpha) check("ungranted management API rejected without relogin", status == 403) status, _, _ = request(platform, "/api/v2/session", credential=alpha) check("forbidden route did not revoke valid token", status == 200) status, _, _ = request(platform, "/api/v2/session", credential={**alpha, "clientId": "wrong-client"}) check("wrong OneOS client ID rejected", status == 401) status, _, _ = request(platform, "/api/v2/auth/oneos/login", {"username": "noaccess", "password": "LocalOnly123!"}) check("valid OneOS identity without entry permission denied", status == 403) status, _, _ = request(platform, "/api/v2/auth/oneos/login", {"username": "alpha", "password": "wrong-password"}) check("wrong password denied without local fallback", status == 401) status, _, _ = request(platform, "/api/v2/auth/oneos/login", {"username": "alpha", "password": "LocalOnly123!"}, headers={"Origin": "http://127.0.0.1:29999"}) check("cross-origin login rejected", status == 403) status, _, _ = request(upstream, "/portal/login", {"username": "alpha", "password": "LocalOnly123!"}, form=True) assert status == 303 portal_token = next(cookie.value for cookie in jar if cookie.name == "fixture_oneos") status, _, hdr = request(upstream, "/portal/ticket", {}) assert status == 303 callback = urllib.parse.urlsplit(hdr["Location"]) assert urllib.parse.urlunsplit((callback.scheme, callback.netloc, "", "", "")) == platform code = urllib.parse.parse_qs(callback.query)["code"][0] status, _, hdr = request(platform, callback.path + "?" + callback.query) check("5-second ticket redeemed before SPA with clean redirect", status == 303 and hdr["Location"] == "/auth/oneos/complete" and "HttpOnly" in hdr.get("Set-Cookie", "")) status, pending, _ = request(platform, "/api/v2/auth/oneos/pending") check("SSO staged identity has no exposed access token", status == 200 and "accessToken" not in pending["data"]) status, _, _ = request(platform, "/api/v2/auth/oneos/confirm", {"csrfToken": "wrong"}) check("SSO wrong CSRF rejected", status == 403) status, confirmed, _ = request(platform, "/api/v2/auth/oneos/confirm", {"csrfToken": pending["data"]["csrfToken"]}) check("SSO returns original OneOS portal token byte-for-byte", status == 200 and confirmed["data"]["accessToken"] == portal_token) check("password and SSO map to same platform subject", confirmed["data"]["session"]["subjectId"] == alpha["session"]["subjectId"]) status, _, _ = request(platform, "/api/v2/auth/oneos/confirm", {"csrfToken": pending["data"]["csrfToken"]}) check("SSO confirmation replay rejected", status in (401, 403)) status, _, _ = request(upstream, "/auth/inner/v1/sso/vehicle-platform/token-exchange", {"code": code}, headers={"Authorization": "Bearer " + cfg["serviceToken"]}) check("consumed upstream ticket cannot be exchanged twice", status == 401) _, _, hdr = request(upstream, "/portal/ticket", {}) expired = urllib.parse.urlsplit(hdr["Location"]) time.sleep(5.1) status, _, hdr = request(platform, expired.path + "?" + expired.query) check("expired 5-second ticket denied with clean URL", status == 303 and "error=" in hdr["Location"] and "code=" not in hdr["Location"]) control("alpha", "revoke-vehicles") status, empty, _ = request(platform, "/api/vehicles", credential=alpha) check("vehicle revocation effective next request", status == 200 and empty["data"]["items"] == []) control("alpha", "restore-vehicles") control("alpha", "disable") status, _, _ = request(platform, "/api/v2/session", credential=alpha) check("OneOS account disable effective next request", status == 401) control("alpha", "enable") status, _, _ = request(platform, "/api/v2/auth/logout", {}, credential=beta) check("unified logout accepted", status == 200) status, _, _ = request(upstream, "/system/user/getInfo", headers={"Authorization": "Bearer " + beta["accessToken"], "ClientID": beta["clientId"]}) check("logout revoked same token at OneOS", status == 401) status, _, _ = request(platform, "/api/v2/session", credential=beta) check("logout revoked same token at platform", status == 401) print(json.dumps({"passed": len(passed), "environment": "literal-loopback-only", "remoteCalls": 0}, ensure_ascii=False)) if __name__ == "__main__": main()