Zustand und Identität · Abschlussprojekt
Abschlussprojekt: Projekt: Wer die Liste ändern darf
Deine Leseliste weiß jetzt, wer fragt. Eine Anmeldung prüft ein Passwort und startet eine Session in einem HttpOnly-/me meldet den Benutzer der Session, und jeder Schreibzugriff braucht ein Token, das schreiben darf. Eines fehlt noch: Eine Session kann beginnen, aber nie enden. Dieses Projekt fügt POST /logout hinzu und prüft dann das ganze Kapitel gegen den Vertrag.
Was der Vertrag verlangt
Öffne SERVICE_CONTRACT.md und lies seinen Abschnitt „Who you are“ als Checkliste:
POST /loginmit den Basic-Zugangsdaten eines bekannten Benutzers: 200,{"user": name}undset-cookie: session=<id>; HttpOnly; Max-Age=3600; Path=/. Alles andere: 401 mit der Basic-Challenge, ohne Cookie und ohne Session.GET /me: 200, mit dem Benutzer der Session odernull.POST /logout: 204 ohne Body und ohnecontent-length; die Session endet, undset-cookielässt das Cookie ablaufen.POST,PUTundDELETEauf Büchern: 401 mitwww-authenticate: Bearerbei einem fehlenden, fehlerhaften oder unbekannten Token, 403 bei einem Token, das nur lesen darf, geprüft vor der Buch-ID und dem Body.405 mit
allow: POSTauf/loginund/logoutundallow: GET, HEADauf/me.Kein Antwort-Body enthält ein Passwort, ein Token oder eine Session-ID.
Jede Zeile außer der Abmeldung sollte bereits gelten. Starte das Szenario who des Hilfsprogramms, um zu sehen, wo du stehst:
python pl_http.py who
Es meldet sich als ana mit Basic-Zugangsdaten an, liest /me mit dem Cookie, das es bekommen hat, sendet POST /logout mit diesem Cookie und Content-Length: 0, liest /me noch einmal mit demselben alten Cookie und sendet zuletzt POST /books mit dem Lese-Token. Heute antwortet die Abmeldung mit HTTP/1.0 404 Not Found, und das zweite /me antwortet immer noch {"user": "ana"}: Die Session lebt.
Eine Session beenden
Abmelden braucht zwei Schritte, und sie sind nicht gleich wichtig.
Erstens entfernt der Server die Session aus state["sessions"]. Das ist der Schritt, der zählt. Sobald die ID weg ist, ist das Cookie wertlos: Jede Anfrage, die es trägt, von diesem Client oder von jemandem, der es kopiert hat, bekommt {"user": null}.
Zweitens bittet die Antwort den Client, das Cookie zu vergessen, mit einem set-cookie, dessen Max-Age 0 ist. Dein session_cookie schreibt das bereits: session_cookie("", max_age=0) ergibt session=""; HttpOnly; Max-Age=0; Path=/, wenn es SimpleCookie verwendet, das den leeren "" schreibt. Ein session=; HttpOnly; Max-Age=0; Path=/. Beide Zeilen lassen das Cookie ablaufen. Ein Cookie-Speicher, der diese Zeile liest, verwirft sein Cookie session sofort. Aber Lektion 1 hat es gesagt: Jedes Attribut ist eine Bitte, die der Client ignorieren kann. Ein Server, der nur diese Zeile sendet und die Session behält, würde das alte Cookie für jeden funktionieren lassen, der eine Kopie behalten hat.
Um die Session zu finden, lies das Cookie session auf dieselbe Weise wie session_user und entferne es mit state["sessions"].pop(value, None). Mit einem Standardwert gibt pop diesen zurück, statt
Meilensteine
Füge in
handleeinen Zweig für/logouthinzu. EinPOSTbeendet die Session, die sein Cookie benennt, falls es eine gibt, und antwortet mit 204, einem leeren Body, ohnecontent-lengthund mit einem Headerset-cookieaussession_cookie("", max_age=0). Jede anderebekommt not_allowed("POST").Führ
python pl_http.py whonoch einmal aus. Die Abmeldung antwortet jetzt mitHTTP/1.0 204 No Content, und das nächste/memit dem alten Cookie antwortet{"user": null}. DasPOST /booksmit dem Lese-Token bekommt weiterhinHTTP/1.0 403 Forbidden. Unter der 204 gibt das Hilfsprogramm die Zeile aus, die das Cookie ablaufen lässt, hier so, wieSimpleCookiesie schreibt:
set-cookie: session=""; HttpOnly; Max-Age=0; Path=/
Du kannst
tests/test_server.pyeinen Test für die Abmeldung hinzufügen.make_requestnimmt einvon Headern, also kann ein Test ein Cookie senden, zum Beispiel:
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}
Führ
python -m pytest -qaus. Jeder Test sollte bestehen, die Tests eingeschlossen, die mit dem Kapitel gekommen sind.
python -m pytest -q
Was geprüft wird
Check my work prüft noch einmal das ganze Kapitel, mit Benutzern, Passwörtern, Tokens und Büchern, die es dir nicht gezeigt hat, und mit Session-IDs, die es selbst wählt, wie es die Prüfung in Lektion 2 getan hat. Es ruft handle und die Hilfsfunktionen direkt für Anmeldungen, /me, Abmeldung und jede Art von Schreibzugriff auf, vergleicht den Zustand vor und nach jeder Ablehnung und durchsucht jede Antwort nach den Zugangsdaten, die es gesendet hat. Es baut die Handler-Klasse deines Servers mit make_server, ohne einen set-cookie und www-authenticate, die zurückkommen. Zuletzt führt es deine Tests einmal aus, so wie sie sind; sie müssen alle bestehen, und keiner darf socket importieren. Das Szenario who in deinem
Drück dann Check my work. Deine
Aufgabe
Füge reading_server.py POST /logout hinzu und sorg dafür, dass jede Regel von „Who you are“ in SERVICE_CONTRACT.md gilt.
POST /logoutentfernt die Session, die seinsessionbenennt, ausstate["sessions"], falls es eine gibt, und antwortet mit 204, dem Bodyb"", ohnecontent-lengthund mit einem Headerset-cookieaussession_cookie("", max_age=0). Ohne Cookie oder bei einer bereits beendeten Session antwortet es trotzdem mit 204. Jede andereauf /logoutbekommt 405 mitallow: POST.Das
POST /logouteines Clients sendet keinen Body, also sendet es, wie jedesPOSTan diesen Server,content-length: 0; ohne das antwortetread_bodymit 411.Alles aus den Lektionen 2, 5 und 6 gilt weiterhin: Basic-Anmeldung mit einer Session in einem
HttpOnly-Cookie oder 401 mit der Basic-Challenge,/memit dem Benutzer odernull, durch Bearer-Token geschützte Schreibzugriffe mit 401 oder 403 je nach Ursache vor der Buch-ID und dem Body, die 405er und kein Passwort, kein Token und keine Session-ID in irgendeiner Antwort.
Führ python pl_http.py who im python -m pytest -q, um deine Tests auszuführen.
Drück dann Check my work. Mit Benutzern, Tokens, Büchern und Session-IDs, die du noch nicht gesehen hast, ruft es handle und die Hilfsfunktionen direkt für Anmeldungen, /me, Abmeldung und jede Art von Schreibzugriff auf, vergleicht den Zustand vor und nach jeder Ablehnung und durchsucht jede Antwort nach Zugangsdaten. Es baut die Handler-Klasse deines Servers mit make_server, ohne einen socket importieren. Nichts aus deinem Terminal wird bewertet.