Request Filter mit Alice

Requests in mehreren Schritten behandeln

  • Requests werden in der Regel von mehreren Handlern hintereinander behandeltDrei Filter und ein Handler, die nacheinander ausgeführt werden
    • entspricht meist verschiedenen logischen Schichten der App
    • z.B. CSRF Schutz → Sitzung überprüfen → Autorisierung → Applikationslogik
  • Die Go Library Alice macht das Verketten von Handlern einfach
    • Handler müssen mit einer Konstruktorfunktion mit vorgegebener Signatur erzeugt werden können:
func MyHandler(http.Handler) http.Handler
  • Handler bearbeiten Request: alles ok → nächster Handler in der Kette, sonst Abbruch der Request-Bearbeitung
    • Alice sorgt dafür, dass die Handler in der richtigen Reihenfolge und mit dem passenden nächsten Handler konstruiert werden
    • So können Ketten von Handlern einfach angelegt werden
anyChecking := alice.New(nosurf.NewPure, controller.SessionChecker, controller.AuthAny)
mux.Handle("/work", anyChecking.ThenFunc(controller.HandleWork))

Auf der Github Seite von Alice ist beschrieben, wie auch Handler mit mehreren Parametern in der Konstruktorfunktion angepasst werden können.

Eigene Request Filter implementieren

Mit wenig Aufwand können eigene Filter implementiert werden. Zuerst brauchen wir ein bischen Infrastruktur-Code:

// filter is called before chaining handlers. Next handler in
// the chain is only called when filter returns true
type filter func(http.ResponseWriter, *http.Request, interface{}) bool

// a struct to chain several handlers for use with alice
type chainableHandler struct {
 	filter filter
 	chain  http.Handler
}

// make chainableHandler an http.Handler
func (c chainableHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if c.filter(w, r, nil) {
 	 	c.chain.ServeHTTP(w, r)
 	}
}
12
Der abstrakte Funktionstyp chainfunc definiert die Signatur für den Filter-Algorithmus. Der 3. Parameter vom Typ interface{} macht die Filterfunktion flexibel, sie kann einen beliebigen weiteren Parameter bekommen.
15-18
Die struct chainableHandler speichert eine Filterfunktion und den Link zum nächsten Handler in der Kette.
21-25
Die struct chainableHandler bekommt eine Methode ServeHTTP(…) und implementiert damit das http.Handler Interface. ServeHTTP ruft den nächsten Handler nur dann auf, wenn die Filterfunktion true liefert.
  • Damit ist die Basis für eigene Filter in der Alice Filterkette vorhanden.
Einfaches Beispiel: ein Request Logger

Die Implementierung eines einfachen Filters besteht mit der oben beschriebenen Infrastruktur aus zwei Schritten:

  1. Filterfunktion implementieren (als Beispiel logRequest(…) ).
  2. Public Konstruktorfunktion RequestLogger anlegen. Diese bettet die Filterfunktion in die struct chainableHandler ein. In Zeile 59 ist ein typecast auf den Funktionstyp chainfunc notwendig.
// logRequest writes relevant information from the request
// to the logging output
func logRequest(w http.ResponseWriter, r *http.Request, ignore interface{}) bool {
	log.Printf("Host: %s, URL: %s, URI: %s\n", r.Host, r.URL.Path, r.RequestURI)
 	return true
}
// RequestLogger embeds logRequest function into
// chainableHandler struct and returns it
func RequestLogger(h http.Handler) http.Handler {
	c := chainableHandler {
		filter: chainfunc(logRequest),
		chain:  h,
	}
	return c
}
Session Checker Filter

Dieser Filter überprüft, ob eine Gorilla Session vorhanden ist. Der Aufbau ist weitgehend analog zum Logging Filter, jedoch treten einige Fälle auf, in denen false zurückgegeben wird. Damit wird der Aufruf der weiteren Handler in der Filterkette unterbunden. In Zeilen 41 und 49 erkennt man, wie der Filter in den Fluss der Webseiten durch ein Redirect eingreifen kann, wenn nach einem Server-Neustart eine ungültige Sitzung entdeckt wird.

// SessionChecker filter checks if there is a valid session,
// i.e if someone is logged in
func SessionChecker(h http.Handler) http.Handler {
	c := chainableHandler{chain: h,
		filter: filter(checkSession)}
	return c
}

// here the session check is actually implemented
func checkSession(w http.ResponseWriter, r *http.Request) bool {
	session, err := SessionStore().Get(r, S_DKFAI)
	if err != nil {
		if err.(scc.Error).IsDecode() {
			// recover from an old hanging session going to login
			http.Redirect(w, r, "/login", http.StatusFound)
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
		return false
	}
	if session.IsNew {
		// no session there, goto login
		http.Redirect(w, r, "/login", http.StatusFound)
		return false
	}
	return true
}
Aufbau von Filtern mit zusätzlichen Parametern

Häufig benötigt man Filterfunktionen, die noch einen zusätzlichen Parameter besitzen. Da Alice nicht vorsieht, dass ein Filter noch weitere Parameter im Konstruktor bekommt, können wir eine eigene Filterfunktion für jeden sinnvollen Wert dieses Parameters anlegen. Das geht natürlich nur, wenn es einen relativ kleinen Wertebereich für diesen Parameter gibt. Die Hilfsfunktion makeFilter(…)  macht die Konstruktion derartiger Filter einfacher, indem sie einer Funktion mit drei Parametern einen Filter mit den für Alice notwendigen 2 Parametern erzeugt.

Wenn die Filterfunktion also weitere Parameter besitzt, müssen diese jeweils für einen bestimmten Wert festgehalten werden. Dadurch wird die Anzahl der Parameter reduziert, wir erhalten eine Funktion vom Typ filter. In der funktionalen Programmierung wird das nach dem Mathematiker Haskel Curry als Currying bezeichnet. Es gibt also ein oder zwei weitere Schritte:

  1. Eine „Currying“-Funktion implementieren, wenn makeFilter nicht passt.
  2. Für jeden sinnvollen Wert der Zusatzparameter eine Konstruktorfunktion für den Filter-Handler anlegen. Das wird weiter unten am Beispiel des Authorization Filter demonstriert.
// reduce additional function parameter to get a filter
func makeFilter(f func(http.ResponseWriter, *http.Request, interface{}) bool,
		mask interface{}) filter {
	return func(w http.ResponseWriter, r *http.Request) bool {
		return f(w, r, mask)
	}
}
89-90
makeFilter ist eine Funktion, die als Parameter eine Funktion
f func(http.ResponseWriter, *http.Request, interface{}) bool  mit drei Parametern und Rückgabetyp bool  sowie ein Interface mask interface{}  besitzt und eine Funktion vom Typ filter  zurückgibt.
92
Die anonyme Funktion, die zurückgegeben wird, ruft in ihrer Implementierung die als Parameter übergebene Funktion f mit dem 3. Parameter mask auf, der damit festgehalten wird.
Authorization Filter

Dieser Filter verwendet die in der Session abgespeicherten Informationen über den aktuellen User, um seine Zugriffsrechte auf eine URL zu überprüfen.Das Beispiel verwendet ein einfaches Rollenmodell zur Rechteverwaltung. Die eigentliche Filterfunktion ist checkAuth(…) .

// checkAuth is the filter function where the actual authorizing is done
func checkAuth(w http.ResponseWriter, r *http.Request, mask interface{}) bool {
	session, e0 := SessionStore().Get(r, S_DKFAI)
	m, ifaceok := mask.(int)
	role, sessionok := session.Values["role"].(int)
	if e0 != nil || !ifaceok || !sessionok {
		http.Error(w, "error validating role", http.StatusInternalServerError)
		return false
	}
	if role & m == 0 {
		http.Error(w, "Not Authorized", http.StatusUnauthorized)
		return false
	}
	return true
}

Wie zu erkennen ist, braucht diese Funktion noch einen zusätzlichen Parameter (Z. 73), der als Maskierung für die role Variable dient (Z. 81). Mit makeFilter(…)  (s.o.) wird daraus ein Filter mit den für Alice notwendigen 2 Parametern erzeugt.

Damit lassen sich jetzt sehr einfach die Autorisierungsfilter für die fünf verschiedenen Rollen erzeugen.

// authorize for anyone who is logged in
func AuthAny(h http.Handler) http.Handler {
	c := chainableHandler{
		filter: makeFilter(checkAuth, model.U_ALL),
		chain:  h,
	}
	return c
}

// authorize for deans office staff for enrolling
func AuthEnrol(h http.Handler) http.Handler {
	c := chainableHandler{
		filter: makeFilter(checkAuth, model.U_ENROL),
		chain:  h,
	}
	return c
}

// authorize for project office staff
func AuthProjectOffice(h http.Handler) http.Handler {
	c := chainableHandler{
		filter: makeFilter(checkAuth, model.U_POFF),
		chain:  h,
	}
	return c
}

// authorize for user administrator
func AuthUserAdmin(h http.Handler) http.Handler {
	c := chainableHandler{
		filter: makeFilter(checkAuth, model.U_UADMIN),
		chain:  h,
	}
	return c
}

// authorize for master administrator
func AuthMasterAdmin(h http.Handler) http.Handler {
	c := chainableHandler{
		filter: makeFilter(checkAuth, model.U_FULLADMIN),
		chain:  h,
	}
	return c
}
99, 108, 117, 126, 135
In diesen Zeilen wird mit makeFilter eine Filterfunktion aus der Funktion checkAuth() erzeugt. Dabei wird jeweils eine Konstante als 3. Parameter an checkAuth übergeben.
97-103
AuthAny() implementiert damit eine Handlerfunktion für die Rolle, die durch die Konstante model.U_ALL  vorgegeben ist.
106-112, 115-121, 124-130, 133-139
Entsprechend werden auch die Filterfunktionen für die anderen definierten Rollen implementiert.

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.