astroklient: strumień do warstwy logicznej też niesie pulę konta
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m1s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m30s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Testy astrodemo (push) Successful in 9m25s
Testy / Testy astroklient (push) Successful in 9m24s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 21s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m25s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Testy astrodemo (pull_request) Successful in 9m25s
Testy / Testy astroklient (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 7s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 5s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m1s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m30s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Testy astrodemo (push) Successful in 9m25s
Testy / Testy astroklient (push) Successful in 9m24s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 21s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m25s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Testy astrodemo (pull_request) Successful in 9m25s
Testy / Testy astroklient (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 7s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 5s
_post dokłada pulę z kontekstu sesji do każdego żądania, ale strumień (używany przez /chart/horoscope/stream) szedł obok niego i wysyłał payload bez zmian. Generowania w tym obrazie dziś nie ma, ale most modułów zostaje celowo — po dołożeniu modułu horoskop strumieniowany szukałby w całym udziale, a pole tenant podsunięte w payloadzie przeszłoby dalej. Sonda w osobnym procesie przepuszcza prawdziwe żądanie przez bramkę sesji do trasy w miejscu modułu (prompt, horoskop, strumień konsumowany leniwie) i podsuwa cudzy login w payloadzie. Przed poprawką strumień wysyłał „ktos-inny", po — konto z sesji. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,12 @@ class LogicClient:
|
||||
"""Strumień NDJSON w dół — przekazywany dalej ramka po ramce.
|
||||
|
||||
Idzie przez szyfrowane łącze jak reszta ruchu (PRE-16): bez tego przy
|
||||
włączonym LINK_ENCRYPTION_REQUIRED serwer odrzuciłby żądanie."""
|
||||
włączonym LINK_ENCRYPTION_REQUIRED serwer odrzuciłby żądanie.
|
||||
|
||||
Nie przechodzi przez `_post`, więc pulę konta dokłada sam — tak samo,
|
||||
z kontekstu i z pierwszeństwem przed polem przyniesionym w payloadzie.
|
||||
Bez tego horoskop strumieniowany szukałby w całym udziale."""
|
||||
payload = {**payload, "tenant": KONTO.get("")}
|
||||
with httpx.Client(timeout=httpx.Timeout(None, connect=15.0)) as client:
|
||||
yield from link_crypto.stream_lines(
|
||||
client, f"{self.base_url}{sciezka}",
|
||||
|
||||
@@ -80,15 +80,66 @@ print("WYNIK:" + json.dumps({
|
||||
'''
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def wynik(drzewo):
|
||||
# Generowania tekstu w tym obrazie nie ma, ale most modułów zostaje — więc jego
|
||||
# transport musi nieść pulę z sesji tak samo jak wywołania ekranów. Osobna sonda,
|
||||
# bo dokłada trasę, której w produkcie nie ma: w sondzie produktu zafałszowałaby
|
||||
# każde pytanie o to, co produkt zawiera.
|
||||
SONDA_MODULU = '''
|
||||
import json, os, hashlib, secrets
|
||||
sol = secrets.token_bytes(16)
|
||||
dk = hashlib.scrypt(b"tajne", salt=sol, n=2**14, r=8, p=1, dklen=32)
|
||||
os.environ.update(ASTROKLIENT_USERS="klient:scrypt$%s$%s" % (sol.hex(), dk.hex()),
|
||||
SESSION_SECRET="t"*64, COOKIES_INSECURE="1", RATE_LIMIT_PER_MIN="0")
|
||||
from fastapi.responses import StreamingResponse
|
||||
from starlette.testclient import TestClient
|
||||
from app.main import app, logic
|
||||
from app import security, session, features
|
||||
from app.clients import logic_client
|
||||
wyslane = {}
|
||||
def call_json(client, method, url, payload=None, **kw):
|
||||
wyslane[url.split("/chart/", 1)[1]] = (payload or {}).get("tenant")
|
||||
return {}
|
||||
def stream_lines(client, url, payload=None, **kw):
|
||||
wyslane[url.split("/chart/", 1)[1]] = (payload or {}).get("tenant")
|
||||
yield '{"type": "result", "result": {}}'
|
||||
logic_client.link_crypto.call_json = call_json
|
||||
logic_client.link_crypto.stream_lines = stream_lines
|
||||
# Trasa w miejscu modułu: woła ten sam transport co moduł generowania, a strumień
|
||||
# konsumuje równie leniwie — dopiero przy wysyłaniu odpowiedzi, nie w obsłudze trasy.
|
||||
features.ROUTES[("POST", "/sonda/modul")] = None
|
||||
@app.post("/sonda/modul")
|
||||
def sonda_modul():
|
||||
podrobione = {"profile": "natal", "tenant": "ktos-inny"}
|
||||
logic.wywolaj("/chart/prompt", podrobione, 5)
|
||||
logic.wywolaj("/chart/horoscope", podrobione, 5)
|
||||
def relay():
|
||||
yield from logic.strumien("/chart/horoscope/stream", podrobione)
|
||||
return StreamingResponse(relay(), media_type="application/x-ndjson")
|
||||
c = TestClient(app, follow_redirects=False)
|
||||
H = {"Cookie": f"{session.COOKIE}={security.issue_session('klient')}"}
|
||||
r = c.post("/sonda/modul", headers=H)
|
||||
print("WYNIK:" + json.dumps({"status": r.status_code, "pule": wyslane}))
|
||||
'''
|
||||
|
||||
|
||||
def _uruchom(drzewo: pathlib.Path, sonda: str) -> dict:
|
||||
"""Aplikacja uruchomiona w OSOBNYM PROCESIE.
|
||||
|
||||
Importy Pythona są zapamiętywane w procesie, więc sprawdzanie „produktu bez
|
||||
modułu" tam, gdzie moduł już się zaimportował, dałoby wynik fałszywie
|
||||
pozytywny."""
|
||||
r = subprocess.run([sys.executable, "-c", SONDA], cwd=drzewo, capture_output=True,
|
||||
r = subprocess.run([sys.executable, "-c", sonda], cwd=drzewo, capture_output=True,
|
||||
text=True, env={"PYTHONPATH": str(drzewo), "PATH": "/usr/bin:/bin"})
|
||||
linia = next((l for l in r.stdout.splitlines() if l.startswith("WYNIK:")), None)
|
||||
assert linia, f"sonda nie wystartowała:\n{r.stdout[-2000:]}\n{r.stderr[-2000:]}"
|
||||
return json.loads(linia[len("WYNIK:"):])
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def wynik(drzewo):
|
||||
return _uruchom(drzewo, SONDA)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def wynik_modulu(drzewo):
|
||||
return _uruchom(drzewo, SONDA_MODULU)
|
||||
|
||||
@@ -44,3 +44,12 @@ def test_the_pool_comes_from_the_session_not_from_the_form(wynik):
|
||||
"""Pula jedzie z zalogowanego konta. Gdyby brała się z żądania, wystarczyłoby
|
||||
podstawić cudzy login, żeby czytać cudze pliki — sonda właśnie to próbuje."""
|
||||
assert wynik["pula"] == "klient"
|
||||
|
||||
|
||||
def test_module_transport_carries_the_session_pool_too(wynik_modulu):
|
||||
"""Prompt i horoskop idą mostem modułów, a strumień horoskopu obok `_post` —
|
||||
i właśnie tam pula ginęła, więc horoskop szukałby w całym udziale. Sonda
|
||||
podsuwa cudzy login w payloadzie; ma wygrać konto z sesji."""
|
||||
assert wynik_modulu["status"] == 200
|
||||
assert wynik_modulu["pule"] == {"prompt": "klient", "horoscope": "klient",
|
||||
"horoscope/stream": "klient"}
|
||||
|
||||
Reference in New Issue
Block a user