0%

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 die hij krijgt. Een test bouwt een verzoek- en roept 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 en een editor, en niets daarin kan een pagina laden.

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 per regel: wat de van een is, welke cookies met een verzoek meegaan en, in hoofdstuk 9, of een pagina een antwoord mag lezen. Een meegeleverde functie, 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 Domain en 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 als null wordt geschreven. Het model kent http- en https-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:

NaamDe rol ervan in deze cursus
https://reading.exampleDe leeslijst: jouw service
https://app.reading.exampleEen tweede front-end voor de leeslijst, die hoofdstuk 9 gebruikt
https://notes.reading.exampleEen host waar lezers eigen pagina’s kunnen publiceren: de buur
https://other.exampleElke 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 . Niets anders in de URL telt mee.

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 with Python laat zien wat er staat en wat niet. Voer dit blok uit:

Try it

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.