Hoofdstuk 8
Origins en cross-site verzoeken
De browser die je gaat modelleren
Elke client die je tot nu toe op je server hebt gericht, doet precies wat hem wordt opgedragen. wire.py stuurt de bytes die je typt. pl_sec.py stuurt de handle() aan.
Een browser is een ander soort client. Hij beslist zelf welke cookies met een verzoek meegaan. Hij beslist of een verzoek überhaupt wordt verstuurd. En hij beslist of de pagina die een verzoek begon het antwoord mag lezen. Iemand opent een pagina of klikt op een knop, en de browser neemt die drie beslissingen zonder iemand iets te vragen.
Je service rekent al op die beslissingen. In hoofdstuk 4 zette je Secure, SameSite=Lax en het voorvoegsel __Host- op de sessiecookie, en elk daarvan is een instructie aan een browser. In hoofdstuk 7 kreeg de service een pagina met een knop Delete bij elk boek. Het enige wat een verwijdering vanaf die pagina autoriseert, is de sessiecookie, die een browser volgens zijn eigen regels meestuurt.
Dit hoofdstuk en het volgende gaan over die regels. Er is één obstakel: deze site heeft geen browser. Je werkomgeving is een
Wat je gaat bouwen, en wat het kost
Dus schrijf je het beslissende deel van een browser zelf. Het bestand mini_browser.py krijgt één kleine visit(), roept je regels voor één verzoek na elkaar aan, en je tests brengen de verzoeken ervan in het geheugen naar handle().
Dat is een model, en een model kost iets. Een echte browser volgt de Fetch-standaard, de HTML-standaard en de cookiestandaard, RFC 6265bis. Samen beslaan ze duizenden regels. Jouw functies komen op een paar dozijn uit. Elke regel die je schrijft, is gebaseerd op een van die standaarden, en elke les zegt wat zijn regel weglaat of vereenvoudigt. Dit zijn de grootste gaten, nog voor er code is:
Geen public suffix list. Een echte browser gebruikt die
om te bepalen welke hosts samen één site vormen. Het model gebruikt een kortere regel, en les 2 legt beide uit. Geen attribuut
Domainen geen verlooptijd. Een cookie hoort in het model bij één exacte host, en hij blijft bestaan tot de server hem verwijdert.Geen frames, geen
, geen caches. Het model kijkt naar één pagina en één verzoek tegelijk. Geen
null-origins. Een echte browser geeft sommige pagina’s een origin die alsnullwordt geschreven. Het model kenthttp- enhttps-URL’s en weigert al het andere.Twee fijnere regels over headers, die hoofdstuk 9 noemt wanneer het ze bereikt.
Geen verschillen tussen browsers. Het model geeft op elke vraag één antwoord. Waar echte browsers verschillende antwoorden geven, zegt de les dat.
Houd nog één grens in gedachten. Als een test in deze hoofdstukken slaagt, is je server tegen het model geslaagd. Niets op deze site draait hem tegen een echte browser.
Wat betekent het dat de browser in deze cursus een model is?
Vier namen
Een browserregel vergelijkt waar een pagina vandaan kwam met waar een verzoek naartoe gaat. Het model heeft dus een paar plaatsen nodig, en de cursus gebruikt van hier tot het eindproject dezelfde vier:
| Naam | De rol ervan in deze cursus |
|---|---|
https://reading.example | De leeslijst: jouw service |
https://app.reading.example | Een tweede front-end voor de leeslijst, die hoofdstuk 9 gebruikt |
https://notes.reading.example | Een host waar lezers eigen pagina’s kunnen publiceren: de buur |
https://other.example | Elke andere site |
Alle vier eindigen op .example, een uitgang die voor voorbeelden is gereserveerd en nooit naar een echte server leidt. Niets in deze cursus zoekt deze namen op. Binnen het model zijn het tekst die je functies uit elkaar halen en vergelijken.
Een origin
Het eerste wat een browser over een URL uitzoekt, is de origin: het schema, de host en de
https://reading.example:8443/books?status=done
|___| |_____________| |__||________________|
scheme host port path and query
|__________________________|
the origin
In https://reading.example:8443/books?status=done is het schema https, de host reading.example en de poort 8443. Die drie vormen de origin, die als https://reading.example:8443 wordt geschreven. Het pad /books en de query status=done horen er niet bij.
De meeste URL’s noemen geen poort. urlsplit uit
De eerste regel van de uitvoer eindigt op None, en de laatste ook: urlsplit meldt geen poort als de URL er geen noemt. De origin-regel vult dat gat. Een ontbrekende poort betekent de standaardpoort van het schema, 443 voor https en 80 voor http. De eerste twee URL’s hebben dus dezelfde origin, en die wordt zonder poort geschreven: https://reading.example. De derde noemt een andere poort en is een andere origin. De vierde heeft een ander schema en is ook een andere origin.
Twee URL’s delen alleen een origin als alle drie de delen overeenkomen.
Welk paar deelt een origin?
Welke twee regels drukt deze code af?
from urllib.parse import urlsplit
for url in ["http://reading.example:8080/x", "https://app.reading.example/books"]:
parts = urlsplit(url)
print(parts.scheme, parts.hostname, parts.port)
Een origin is de eenheid die een browser beschermt, en les 4 zegt wat die bescherming omvat. In deze les werd nergens iets naartoe gestuurd. In de volgende schrijf je de origin als functie, en voeg je een grovere eenheid toe waar cookies zich naar richten.