CORS · oefening
Antwoord vanuit een exacte allowlist
Les 1 volgde de uitwisseling op papier. Nu schrijf je beide helften ervan. De helft van de server is cors_headers in reading_server.py, die beslist wat het antwoord zegt. De helft van de browser is may_read in mini_browser.py, die beslist wat de pagina krijgt. Check my work test je server tegen een eigen browsermodel, en je model tegen eigen antwoorden, zodat de twee helften het niet over een fout eens kunnen worden.
De helft van de server
cors_headers(request, trusted) geeft een trusted is een set state["cors_origins"], en die bevat één origin: https://app.reading.example.
De beslissing is dezelfde die origin_allowed in hoofdstuk 8 nam. Lees de Origin van het verzoek. Als er precies één is, en de set bevat die, noemt het antwoord diezelfde origin:
access-control-allow-origin: https://app.reading.example
In alle andere gevallen heeft het antwoord zo’n header niet. De server weigert niets: hij stuurt zijn gewone antwoord en laat de toestemming weg.
De vergelijking is values[0] in trusted, de hele tekst tegen de leden van een set. Drie kortere wegen lijken minder werk, en elk geeft iets weg:
De header
Originin het antwoord kopiëren. Elke pagina die het vraagt, krijgt te horen dat zij, bij naam, mag lezen. De header staat dan elke origin toe die er bestaat.Een test met
startswithofinop de tekst.https://app.reading.example.other.examplebegint met de vertrouwde origin, en hij is van wieother.examplebezit. Hoofdstuk 8 liet deze lookalikes zien.nullin de set zetten. Een browser schrijft de origin van sommige pagina’s, bijvoorbeeld een pagina in een sandbox, als de tekstnull. Een set die"null"bevat, staat elke zulke pagina toe, wie hem ook gemaakt heeft.
Een server kopieert de Origin van het verzoek op elk antwoord naar Access-Control-Allow-Origin. Welke pagina's mogen zijn antwoorden lezen?
Vary: Origin
Tot nu toe gaf /about iedereen hetzelfde antwoord. Vanaf deze les hangen de headers ervan af van een header van het verzoek.
Dat is van belang voor een cache. In /about zo bewaart, zou het eerste antwoord houden dat hij zag en dat aan iedereen geven. Als dat eerste antwoord naar een pagina op een andere origin ging, zit er geen toestemming in, en de frontend krijgt het en mag niet lezen. Met twee toegestane origins zou elk een antwoord kunnen krijgen dat de andere noemt. De cache geeft niemand een leesrecht dat hij niet hoort te hebben: een pagina die het antwoord voor de frontend krijgt, zakt nog steeds voor de controle van de browser, omdat de header een andere origin noemt. Wat er stukgaat, zijn de lezers die je wel toestond.
Nog één
vary: Origin
Hij vertelt een cache dat het antwoord afhangt van de header Origin van het verzoek, zodat antwoorden voor verschillende origins gescheiden moeten blijven. De weigering hangt evenveel van die header af als de toestemming. Daarom zet cors_headers vary als eerste in zijn dict en geeft hij die in elk geval terug, ook voor een verzoek zonder header Origin.
Niets in deze cursus laat Vary aan het werk zien, omdat het model geen cache heeft. De reden die hier staat, is het hele Vary al om een andere reden zet, zou de twee waarden moeten samenvoegen. Deze heeft geen andere reden.
Waarom draagt het antwoord Vary: Origin, ook als de origin wordt geweigerd?
Wanneer een ster prima is
Access-Control-Allow-Origin: * staat elke origin toe. Dat is een redelijk antwoord voor een antwoord dat voor iedereen hetzelfde is en geen login nodig heeft: iedereen kan het toch al ophalen. Het is nooit goed voor een antwoord achter een login. Browsers weigeren een ster zodra de pagina vroeg om
/about zou in aanmerking komen voor een ster. Deze service noemt ook daar de origin, zodat hij één regel te controleren heeft en geen twee.
Wanneer is Access-Control-Allow-Origin: * aanvaardbaar?
De helft van de browser
may_read(response, page_origin, *, credentials) is de controle die je in les 1 uitvoerde, als een True of False teruggeeft. De meegeleverde response_header(response, name) geeft je één header van een antwoord als tekst, of None.
Geen
access-control-allow-origin: nee.De waarde
*, of preciespage_origin: ja.Elke andere waarde: nee. Dat geldt ook voor een waarde met een slash aan het eind, twee origins in één waarde, en dezelfde letters met andere hoofdletters.
credentials zegt of de pagina vroeg om cookies mee te sturen met haar verzoek. Les 5 schrijft de regel voor dat geval. Begin de functie tot dan met if credentials: return False. Het model weigert wat het nog niet begrijpt, zoals load_keys deed in hoofdstuk 5.
Je opdracht
Schrijf in reading_server.py de functie cors_headers. Gebruik hem daarna in route(). Voeg in de tak "/about", in het geval voor GET en HEAD, één regel toe nadat het antwoord is gemaakt:
response = add_headers(response, cors_headers(request, state["cors_origins"]))
De meegeleverde add_headers kopieert een dict met headers naar een antwoord en geeft het antwoord terug. Vraag je functie wat hij antwoordt:
python -c "import reading_server; print(reading_server.cors_headers({'headers': {'origin': ['https://app.reading.example']}}, {'https://app.reading.example'}))"
Dat drukt {'vary': 'Origin', 'access-control-allow-origin': 'https://app.reading.example'} af. Met elke andere origin in het verzoek drukt het {'vary': 'Origin'} af.
Schrijf in mini_browser.py de functie may_read.
Schrijf daarna één test, test_a_page_without_permission_cannot_read, in tests/test_browser.py. Die stuurt GET /about met de header Origin: https://app.reading.example rechtstreeks naar reading_server.handle, en geeft het antwoord twee keer aan mini_browser.may_read: voor een pagina op steps.APP, die het mag lezen, en voor een pagina op steps.OTHER, die dat niet mag. visit() kan dit verzoek nog niet voor je doen. Daarvoor is een functie nodig die les 5 schrijft.
python -m pytest -q
python mini_browser.py
De eerste eindigt met 36 passed. In de tweede tabel van python mini_browser.py zegt de eerste kolom “may read” nu True voor de frontend en False voor de andere twee pagina’s. De laatste kolom zegt drie keer False, en blijft zo tot les 5.
Druk daarna op Check my work.
Opdracht
Schrijf het
In reading_server.py:
cors_headers(request, trusted)geeft eenmet headers terug. Die bevat altijd "vary": "Origin". Als het verzoek precies éénoriginheeft en de settrusteddie waarde bevat, bevat hij ook"access-control-allow-origin"met diezelfde waarde. Hij antwoordt nooit*, en hij vergelijkt de hele waarde met de leden van de set.In de tak
"/about"vanroute(), in het geval voor GET en HEAD:response = add_headers(response, cors_headers(request, state["cors_origins"])).
In mini_browser.py:
may_read(response, page_origin, *, credentials)geeftFalseterug alscredentialswaar is. Anders leest hijresponse_header(response, "access-control-allow-origin")en geeft hijTrueterug als de waarde"*"is of gelijk is aanpage_origin, enFalsevoor een ontbrekende header en voor elke andere waarde.
Schrijf in tests/test_browser.py, onder het commentaar voor hoofdstuk 9, test_a_page_without_permission_cannot_read. Bouw helpers.make_request("GET", "/about", headers={"Origin": steps.APP}), geef dat aan reading_server.handle(request, state) met state = reading_server.sample_state(), en assert dat mini_browser.may_read(response, steps.APP, credentials=False) True is en dat mini_browser.may_read(response, steps.OTHER, credentials=False) False is.
Voer python -m pytest -q en python mini_browser.py uit.
Druk daarna op Check my work. Dat roept cors_headers aan met verzoeken en een set van twee http-vorm, de tekst null, twee headers Origin, en geen enkele. Het stuurt GET en HEAD /about naar handle() vanaf drie origins en zonder origin, en laat een eigen browsermodel de antwoorden beoordelen. Het roept may_read aan met eigen antwoorden: geen header, de exacte origin, een ster, een toegestane 404, en acht waarden die alleen goed lijken, en twee keer met credentials=True. Als laatste voert het je test één keer bij naam uit: die moet slagen, en hij moet reading_server.handle en mini_browser.may_read via hun