0%

CORS · Übung

Antworte aus einer exakten Allowlist

Lektion 1 hat den Austausch auf dem Papier verfolgt. Jetzt schreibst du beide Hälften. Die Hälfte des Servers ist cors_headers in reading_server.py, das entscheidet, was die Antwort sagt. Die Hälfte des Browsers ist may_read in mini_browser.py, das entscheidet, was die Seite bekommt. Check my work prüft deinen Server gegen ein eigenes Browsermodell und dein Modell gegen eigene Antworten, damit sich die beiden Hälften nicht auf einen Fehler einigen können.

Die Hälfte des Servers

cors_headers(request, trusted) gibt ein mit Headern zurück, die einer Antwort hinzugefügt werden. trusted ist ein Set von . Der Zustand hat für dieses Kapitel ein solches Set, state["cors_origins"], und es enthält einen Origin: https://app.reading.example.

Die Entscheidung ist die, die origin_allowed in Kapitel 8 getroffen hat. Lies die des Headers Origin der Anfrage. Wenn es genau einen gibt und das Set ihn enthält, nennt die Antwort genau diesen Origin:

access-control-allow-origin: https://app.reading.example

In jedem anderen Fall hat die Antwort keinen solchen Header. Der Server lehnt nichts ab: Er sendet seine übliche Antwort und lässt die Erlaubnis weg.

Der Vergleich ist values[0] in trusted, der ganze Text gegen die Elemente eines Sets. Drei Abkürzungen sehen nach weniger Arbeit aus, und jede gibt etwas preis:

  • Den Header Origin in die Antwort kopieren. Jeder Seite, die fragt, wird gesagt, dass gerade sie mit Namen lesen darf. Der Header erlaubt dann jeden Origin, den es gibt.

  • Ein Test mit startswith oder in auf dem Text. https://app.reading.example.other.example beginnt mit dem vertrauenswürdigen Origin, und es gehört dem, dem other.example gehört. Kapitel 8 hat diese Doppelgänger gezeigt.

  • null in das Set aufnehmen. Ein Browser schreibt den Origin mancher Seiten, zum Beispiel einer Seite in einer Sandbox, als den Text null. Ein Set, das "null" enthält, erlaubt jede solche Seite, egal wer sie gemacht hat.

Ein Server kopiert den Origin der Anfrage bei jeder Antwort in Access-Control-Allow-Origin. Welche Seiten dürfen seine Antworten lesen?

Vary: Origin

Bisher hat /about allen dieselbe Antwort gegeben. Ab dieser Lektion hängen seine Header von einem Header der Anfrage ab.

Das ist für einen Cache wichtig. In with Python hat ein Cache eine Antwort unter ihrer gespeichert. Ein Cache, der zwischen vielen Lesern und deinem Server sitzt und /about so speichert, würde die erste Antwort behalten, die er sieht, und sie allen geben. Wenn diese erste Antwort an eine Seite auf einem anderen Origin ging, steht keine Erlaubnis darin, und das Frontend bekommt sie und darf nicht lesen. Bei zwei erlaubten Origins könnte jeder eine Antwort bekommen, die den anderen nennt. Der Cache gibt niemandem einen Lesezugriff, den er nicht haben sollte: Eine Seite, die die Antwort des Frontends bekommt, fällt trotzdem durch die Prüfung des Browsers, weil der Header einen anderen Origin nennt. Was kaputtgeht, sind die Leser, die du erlaubt hast.

Ein weiterer verhindert das:

vary: Origin

Er sagt einem Cache, dass die Antwort vom Header Origin der Anfrage abhängt, also müssen Antworten für verschiedene Origins getrennt gehalten werden. Die Ablehnung hängt genauso von diesem Header ab wie die Erlaubnis. Deshalb setzt cors_headers vary als Erstes in sein Dictionary und gibt es in jedem Fall zurück, auch für eine Anfrage ganz ohne Header Origin.

Nichts in diesem Kurs zeigt Vary bei der Arbeit, weil das Modell keinen Cache hat. Die Begründung hier ist das ganze . Ein Server, der Vary schon aus einem anderen Grund setzt, müsste die beiden Werte zusammenführen. Dieser hat keinen anderen.

Warum trägt die Antwort Vary: Origin auch dann, wenn der Origin abgelehnt wird?

Wann ein Stern in Ordnung ist

Access-Control-Allow-Origin: * erlaubt jeden Origin. Das ist eine faire Antwort für eine Antwort, die für alle gleich ist und keine Anmeldung braucht: Jeder könnte sie ohnehin abrufen. Für eine Antwort hinter einer Anmeldung ist sie nie richtig. Browser lehnen einen Stern ab, sobald die Seite verlangt hat, dass ihre Anfrage trägt, und Lektion 5 kommt auf diese Regel zurück.

/about käme für einen Stern infrage. Dieser Dienst nennt auch dort den Origin, damit er eine Regel zu prüfen hat und nicht zwei.

Wann ist Access-Control-Allow-Origin: * akzeptabel?

Die Hälfte des Browsers

may_read(response, page_origin, *, credentials) ist die Prüfung, die du in Lektion 1 ausgeführt hast, als , die True oder False zurückgibt. Das mitgelieferte response_header(response, name) gibt dir einen Header einer Antwort als Text, oder None.

  • Kein access-control-allow-origin: nein.

  • Der Wert * oder genau page_origin: ja.

  • Jeder andere Wert: nein. Das schließt einen Wert mit einem Schrägstrich am Ende ein, zwei Origins in einem Wert und dieselben Buchstaben in anderer Groß- und Kleinschreibung.

credentials sagt, ob die Seite verlangt hat, dass ihre Anfrage Cookies trägt. Lektion 5 schreibt die Regel für diesen Fall. Bis dahin beginnst du die Funktion mit if credentials: return False. Das Modell lehnt ab, was es noch nicht versteht, so wie load_keys in Kapitel 5.

Deine Aufgabe

Schreibe in reading_server.py die Funktion cors_headers. Verwende sie dann in route(). Füge im Zweig "/about", im Fall für GET und HEAD, eine Zeile hinzu, nachdem die Antwort erstellt ist:

            response = add_headers(response, cors_headers(request, state["cors_origins"]))

Das mitgelieferte add_headers kopiert ein Dictionary mit Headern in eine Antwort und gibt die Antwort zurück. Frag deine Funktion, was sie antwortet:

python -c "import reading_server; print(reading_server.cors_headers({'headers': {'origin': ['https://app.reading.example']}}, {'https://app.reading.example'}))"

Es gibt {'vary': 'Origin', 'access-control-allow-origin': 'https://app.reading.example'} aus. Mit jedem anderen Origin in der Anfrage gibt es {'vary': 'Origin'} aus.

Schreibe in mini_browser.py die Funktion may_read.

Schreibe dann einen Test, test_a_page_without_permission_cannot_read, in tests/test_browser.py. Er sendet GET /about mit dem Header Origin: https://app.reading.example direkt an reading_server.handle und gibt die Antwort zweimal an mini_browser.may_read: für eine Seite auf steps.APP, die sie lesen darf, und für eine Seite auf steps.OTHER, die es nicht darf. visit() kann diese Anfrage noch nicht für dich stellen. Es braucht eine Funktion, die Lektion 5 schreibt.

python -m pytest -q
python mini_browser.py

Der erste endet mit 36 passed. In der zweiten Tabelle von python mini_browser.py sagt die erste Spalte „may read“ jetzt True für das Frontend und False für die anderen beiden Seiten. Die letzte Spalte sagt dreimal False und bleibt so bis Lektion 5.

Klicke dann auf Check my work.

Aufgabe

Schreibe die -Antwort des Servers und die Prüfung des Browsers dazu, und einen Test.

In reading_server.py:

  • cors_headers(request, trusted) gibt ein mit Headern zurück. Es enthält immer "vary": "Origin". Wenn die Anfrage genau einen für origin hat und das Set trusted diesen Wert enthält, enthält es außerdem "access-control-allow-origin" mit genau diesem Wert. Es antwortet nie *, und es vergleicht den ganzen Wert mit den Elementen des Sets.

  • Im Zweig "/about" von route(), im Fall für GET und HEAD: response = add_headers(response, cors_headers(request, state["cors_origins"])).

In mini_browser.py:

  • may_read(response, page_origin, *, credentials) gibt False zurück, wenn credentials wahr ist. Sonst liest es response_header(response, "access-control-allow-origin") und gibt True zurück, wenn der Wert "*" ist oder gleich page_origin, und False für einen fehlenden Header und für jeden anderen Wert.

Schreibe in tests/test_browser.py unter dem Kommentar zu Kapitel 9 den Test test_a_page_without_permission_cannot_read. Baue helpers.make_request("GET", "/about", headers={"Origin": steps.APP}), gib es mit state = reading_server.sample_state() an reading_server.handle(request, state) und sichere zu, dass mini_browser.may_read(response, steps.APP, credentials=False) True ist und dass mini_browser.may_read(response, steps.OTHER, credentials=False) False ist.

Führe python -m pytest -q und python mini_browser.py aus.

Klicke dann auf Check my work. Es ruft cors_headers mit Anfragen und einem Set von zwei auf, die du noch nicht gesehen hast: beide vertrauenswürdigen Origins, eine andere Site, ein benachbarter Host, Hosts, die nur wie ein vertrauenswürdiger aussehen, ein anderer , die Form mit http, der Text null, zwei Header Origin und keiner. Es sendet GET und HEAD /about von drei Origins und ohne einen an handle() und lässt ein eigenes Browsermodell die Antworten beurteilen. Es ruft may_read mit eigenen Antworten auf: kein Header, der exakte Origin, ein Stern, eine erlaubte 404 und acht Werte, die nur richtig aussehen, und zweimal mit credentials=True. Zuletzt führt es deinen Test einmal mit Namen aus: Er muss bestehen, und er muss reading_server.handle und mini_browser.may_read über ihre aufrufen. Nichts aus deinem wird bewertet.