0%

Toestand en identiteit · eindproject

Eindproject: Project: wie mag de lijst wijzigen

Je leeslijst weet nu wie er vraagt. Een login controleert een wachtwoord en start een sessie in een HttpOnly-, /me meldt de gebruiker van de sessie, en elke schrijfactie heeft een token nodig dat mag schrijven. Eén ding ontbreekt: een sessie kan beginnen, maar nooit eindigen. Dit project voegt POST /logout toe en controleert daarna het hele hoofdstuk tegen het contract.

Wat het contract vraagt

Open SERVICE_CONTRACT.md en lees de sectie “Who you are” als checklist:

  • POST /login met de Basic-inloggegevens van een bekende gebruiker: 200, {"user": name}, en set-cookie: session=<id>; HttpOnly; Max-Age=3600; Path=/. Al het andere: 401 met de Basic-challenge, geen cookie en geen sessie.

  • GET /me: 200, met de gebruiker van de sessie of null.

  • POST /logout: 204 zonder body en zonder content-length; de sessie eindigt, en set-cookie laat de cookie verlopen.

  • POST, PUT en DELETE op boeken: 401 met www-authenticate: Bearer voor een ontbrekend, misvormd of onbekend token, 403 voor een token dat alleen mag lezen, gecontroleerd vóór de boek-id en de body.

  • 405 met allow: POST op /login en /logout, en allow: GET, HEAD op /me.

  • Geen enkele body van een antwoord bevat een wachtwoord, een token of een sessie-id.

Elke rij behalve uitloggen zou al moeten kloppen. Voer het scenario who van het hulpprogramma uit om te zien waar je staat:

python pl_http.py who

Het logt in als ana met Basic-inloggegevens, leest /me met de cookie die het kreeg, stuurt POST /logout met die cookie en Content-Length: 0, leest /me opnieuw met dezelfde oude cookie, en stuurt als laatste POST /books met het leestoken. Vandaag antwoordt het uitloggen met HTTP/1.0 404 Not Found, en de tweede /me antwoordt nog steeds {"user": "ana"}: de sessie leeft.

Een sessie beëindigen

Uitloggen kost twee stappen, en ze zijn niet even belangrijk.

Eerst verwijdert de server de sessie uit state["sessions"]. Dit is de stap die telt. Zodra de id weg is, is de cookie waardeloos: elk verzoek dat hem draagt, van deze client of van iemand die hem heeft gekopieerd, krijgt {"user": null}.

Daarna vraagt het antwoord de client de cookie te vergeten, met een set-cookie waarvan Max-Age 0 is. Je session_cookie schrijft dat al: session_cookie("", max_age=0) geeft session=""; HttpOnly; Max-Age=0; Path=/ als hij SimpleCookie gebruikt, dat de lege als "" schrijft. Een schrijft session=; HttpOnly; Max-Age=0; Path=/. Beide regels laten de cookie verlopen. Een jar die deze regel leest, laat zijn cookie session meteen vallen. Maar les 1 zei het al: elk attribuut is een verzoek dat de client mag negeren. Een server die alleen deze regel stuurde en de sessie hield, zou de oude cookie laten werken voor iedereen die een kopie had bewaard.

Om de sessie te vinden, lees je de cookie session op dezelfde manier als session_user doet, en verwijder je hem met state["sessions"].pop(value, None). Met een standaardwaarde geeft pop die terug in plaats van op te werpen als de sleutel ontbreekt, wat gebeurt bij een onbekende of al beëindigde sessie. Uitloggen zonder sessie, of twee keer achter elkaar, is geen fout: de client is hoe dan ook uitgelogd, dus elk antwoordt met 204. Niets op deze server beëindigt een sessie na verloop van tijd, dus uitloggen is de enige manier waarop er een eindigt voordat de server stopt.

Mijlpalen

  1. Voeg in handle een tak toe voor /logout. Een POST beëindigt de sessie die zijn cookie aanduidt, als die er is, en antwoordt met 204 met een lege body, geen content-length, en een header set-cookie van session_cookie("", max_age=0). Elke andere krijgt not_allowed("POST").

  2. Voer python pl_http.py who opnieuw uit. Het uitloggen antwoordt nu met HTTP/1.0 204 No Content, en de volgende /me met de oude cookie antwoordt {"user": null}. De POST /books met het leestoken krijgt nog steeds HTTP/1.0 403 Forbidden. Onder de 204 drukt het hulpprogramma de regel af die de cookie laat verlopen, hier zoals SimpleCookie hem schrijft:

  set-cookie: session=""; HttpOnly; Max-Age=0; Path=/
  1. Je kunt een test voor uitloggen toevoegen aan tests/test_server.py. make_request neemt een met headers, dus een test kan een cookie sturen, bijvoorbeeld:

def test_logout_ends_the_session():
    state = reading_server.sample_state()
    session_id = reading_server.start_session(state, "ana")
    cookie = {"cookie": "session=" + session_id}
    response = reading_server.handle(make_request("POST", "/logout", headers=cookie), state)
    assert response["status"] == 204
    me = reading_server.handle(make_request("GET", "/me", headers=cookie), state)
    assert json.loads(me["body"].decode("utf-8")) == {"user": None}
  1. Voer python -m pytest -q uit. Elke test zou moeten slagen, ook de tests die bij het hoofdstuk hoorden.

python -m pytest -q

Wat er wordt gecontroleerd

Check my work controleert het hele hoofdstuk opnieuw met gebruikers, wachtwoorden, tokens en boeken die hij je niet heeft laten zien, en met sessie-id’s die hij zelf kiest, zoals de controle van les 2 deed. Hij roept handle en de hulpfuncties rechtstreeks aan voor logins, /me, uitloggen en elke soort schrijfactie, vergelijkt de state voor en na elke weigering, en doorzoekt elk antwoord op de inloggegevens die hij stuurde. Hij bouwt de handler-klasse van je server met make_server zonder een te openen, stuurt er een login en een schrijfactie zonder token als bytes doorheen, en leest de regels set-cookie en www-authenticate die terugkomen. Als laatste voert hij je tests één keer uit, zoals ze zijn; ze moeten allemaal slagen, en geen ervan mag socket importeren. Het scenario who in je is om naar te kijken; niets ervan wordt beoordeeld.

Klik daarna op Check my work. Je zegt nu wie hem mag wijzigen. Elke lezer downloadt echter nog steeds de hele lijst elke keer dat hij erom vraagt, ook als er sinds de vorige keer niets is veranderd. Hoofdstuk 8 leert de server “er is niets veranderd” te zeggen en de client te onthouden wat hij al heeft.

Opdracht

Voeg POST /logout toe aan reading_server.py, en laat elke regel van “Who you are” in SERVICE_CONTRACT.md kloppen.

  • POST /logout verwijdert de sessie die zijn session aanduidt uit state["sessions"], als die er is, en antwoordt met 204 met de body b"", geen content-length, en een header set-cookie van session_cookie("", max_age=0). Zonder cookie, of voor een sessie die al beëindigd is, antwoordt hij nog steeds met 204. Elke andere op /logout krijgt 405 met allow: POST.

  • De POST /logout van een client stuurt geen body, dus stuurt hij, zoals elke POST naar deze server, content-length: 0; zonder die header antwoordt read_body met 411.

  • Alles uit les 2, 5 en 6 klopt nog steeds: Basic-login met een sessie in een HttpOnly-cookie of 401 met de Basic-challenge, /me met de gebruiker of null, met bearer-tokens beschermde schrijfacties met 401 of 403 naar oorzaak vóór de boek-id en de body, de 405’s, en geen wachtwoord, token of sessie-id in enig antwoord.

Voer python pl_http.py who uit in de om in te loggen, uit te loggen en te zien dat de oude cookie niet meer werkt, en python -m pytest -q om je tests uit te voeren.

Klik daarna op Check my work. Met gebruikers, tokens, boeken en sessie-id’s die je nog niet hebt gezien, roept hij handle en de hulpfuncties rechtstreeks aan voor logins, /me, uitloggen en elke soort schrijfactie, vergelijkt hij de state voor en na elke weigering, en doorzoekt hij elk antwoord op inloggegevens. Hij bouwt de handler-klasse van je server met make_server zonder een te openen en stuurt er een login en een schrijfactie zonder token als bytes doorheen. Als laatste voert hij je tests één keer uit, zoals ze zijn: ze moeten allemaal slagen, en geen ervan mag socket importeren. Niets uit je terminal wordt beoordeeld.