changeset 419:6627c48363a0

First attempt for user injection of proxy for using GeoServer with role based security.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 16 Aug 2018 13:39:13 +0200
parents c70ddc6eb168
children be38eec5cc25
files pkg/controllers/proxy.go pkg/controllers/routes.go pkg/middleware/modifyquery.go
diffstat 3 files changed, 159 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/proxy.go	Thu Aug 16 13:14:46 2018 +0200
+++ b/pkg/controllers/proxy.go	Thu Aug 16 13:39:13 2018 +0200
@@ -152,49 +152,52 @@
 	}
 }
 
-func proxyModifyResponse(resp *http.Response) error {
+func proxyModifyResponse(suffix string) func(*http.Response) error {
+
+	return func(resp *http.Response) error {
+
+		if !isXML(resp.Header) {
+			return nil
+		}
+
+		pr, pw := io.Pipe()
+
+		var (
+			r   io.ReadCloser
+			w   io.WriteCloser
+			err error
+		)
+
+		reader, writer := encoding(resp.Header)
+
+		if r, err = reader(resp.Body); err != nil {
+			return err
+		}
 
-	if !isXML(resp.Header) {
+		if w, err = writer(pw); err != nil {
+			return err
+		}
+
+		go func(force io.ReadCloser) {
+			start := time.Now()
+			defer func() {
+				//r.Close()
+				w.Close()
+				pw.Close()
+				force.Close()
+				log.Printf("rewrite took %s\n", time.Since(start))
+			}()
+			if err := rewrite(suffix, w, r); err != nil {
+				log.Printf("rewrite failed: %v\n", err)
+				return
+			}
+			log.Println("rewrite successful")
+		}(resp.Body)
+
+		resp.Body = pr
+
 		return nil
 	}
-
-	pr, pw := io.Pipe()
-
-	var (
-		r   io.ReadCloser
-		w   io.WriteCloser
-		err error
-	)
-
-	reader, writer := encoding(resp.Header)
-
-	if r, err = reader(resp.Body); err != nil {
-		return err
-	}
-
-	if w, err = writer(pw); err != nil {
-		return err
-	}
-
-	go func(force io.ReadCloser) {
-		start := time.Now()
-		defer func() {
-			//r.Close()
-			w.Close()
-			pw.Close()
-			force.Close()
-			log.Printf("rewrite took %s\n", time.Since(start))
-		}()
-		if err := rewrite(w, r); err != nil {
-			log.Printf("rewrite failed: %v\n", err)
-			return
-		}
-		log.Println("rewrite successful")
-	}(resp.Body)
-
-	resp.Body = pr
-
-	return nil
 }
 
 var xmlContentTypes = []string{
@@ -217,10 +220,10 @@
 
 var replaceRe = regexp.MustCompile(`\b(https?://[^\s\?]*)`)
 
-func replace(s string) string {
+func replace(suffix, s string) string {
 
 	proxyKey := config.ProxyKey()
-	proxyPrefix := config.ProxyPrefix() + "/api/proxy/"
+	proxyPrefix := config.ProxyPrefix() + suffix
 
 	return replaceRe.ReplaceAllStringFunc(s, func(s string) string {
 		if _, found := proxyBlackList[s]; found {
@@ -237,7 +240,7 @@
 	})
 }
 
-func rewrite(w io.Writer, r io.Reader) error {
+func rewrite(suffix string, w io.Writer, r io.Reader) error {
 
 	decoder := xml.NewDecoder(r)
 	decoder.CharsetReader = charset.NewReaderLabel
@@ -264,7 +267,7 @@
 			n = n.push()
 
 			for i := range t.Attr {
-				t.Attr[i].Value = replace(t.Attr[i].Value)
+				t.Attr[i].Value = replace(suffix, t.Attr[i].Value)
 				n.checkDef(&t.Attr[i])
 			}
 
@@ -284,7 +287,7 @@
 			tok = t
 
 		case xml.CharData:
-			tok = xml.CharData(replace(string(t)))
+			tok = xml.CharData(replace(suffix, string(t)))
 
 		case xml.EndElement:
 			s := n.lookup(t.Name.Space)
--- a/pkg/controllers/routes.go	Thu Aug 16 13:14:46 2018 +0200
+++ b/pkg/controllers/routes.go	Thu Aug 16 13:39:13 2018 +0200
@@ -7,6 +7,7 @@
 	"github.com/gorilla/mux"
 
 	"gemma.intevation.de/gemma/pkg/auth"
+	"gemma.intevation.de/gemma/pkg/middleware"
 )
 
 func BindRoutes(m *mux.Router) {
@@ -54,10 +55,10 @@
 	// Proxy for external WFSs.
 	proxy := &httputil.ReverseProxy{
 		Director:       proxyDirector,
-		ModifyResponse: proxyModifyResponse,
+		ModifyResponse: proxyModifyResponse("/api/proxy/"),
 	}
 
-	api.Handle(`/proxy/{hash}/{url}`, proxy).
+	api.Handle("/proxy/{hash}/{url}", proxy).
 		Methods(
 			http.MethodGet, http.MethodPost,
 			http.MethodPut, http.MethodDelete)
@@ -67,6 +68,25 @@
 			http.MethodGet, http.MethodPost,
 			http.MethodPut, http.MethodDelete)
 
+	// Proxy for external WFSs.
+	internal := &httputil.ReverseProxy{
+		Director:       proxyDirector,
+		ModifyResponse: proxyModifyResponse("/api/internal"),
+	}
+
+	internalAuth := all(
+		middleware.ModifyQuery(internal, middleware.InjectUser))
+
+	api.Handle("/internal/{hash}/{url}", internalAuth).
+		Methods(
+			http.MethodGet, http.MethodPost,
+			http.MethodPut, http.MethodDelete)
+
+	api.Handle("/internal/{entry}", internalAuth).
+		Methods(
+			http.MethodGet, http.MethodPost,
+			http.MethodPut, http.MethodDelete)
+
 	// Token handling: Login/Logout.
 	api.HandleFunc("/login", login).
 		Methods(http.MethodGet, http.MethodPost)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/middleware/modifyquery.go	Thu Aug 16 13:39:13 2018 +0200
@@ -0,0 +1,89 @@
+package middleware
+
+import (
+	"log"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"gemma.intevation.de/gemma/pkg/auth"
+)
+
+// ParseQuery is a modified version of the internal query
+// parser of the url.parseQuery of the standard library.
+func ParseQuery(
+	m url.Values,
+	query string,
+	keySep, valueSep string,
+	unescape func(string) (string, error),
+) error {
+	if unescape == nil {
+		unescape = url.QueryUnescape
+	}
+	for query != "" {
+		key := query
+		if i := strings.Index(key, keySep); i >= 0 {
+			key, query = key[:i], key[i+1:]
+
+		} else {
+			query = ""
+		}
+		if key == "" {
+			continue
+		}
+		value := ""
+		if i := strings.Index(key, valueSep); i >= 0 {
+			key, value = key[:i], key[i+1:]
+		}
+		key, err := unescape(key)
+		if err != nil {
+			return err
+		}
+		value, err = unescape(value)
+		if err != nil {
+			return err
+		}
+		m[key] = append(m[key], value)
+	}
+	return nil
+}
+
+func ModifyQuery(next http.Handler, modify func(*http.Request, url.Values) error) http.Handler {
+
+	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+
+		// GeoServer query parameters contain ';' as sub key separators.
+		// If we would use req.URL.Query() this would be split
+		// at the wrong level resulting in broken key/value pairs.
+		// So we do the splitting ourselves.
+
+		parameters := make(url.Values)
+
+		if err := ParseQuery(parameters, req.URL.RawQuery, "&", "=", nil); err != nil {
+			log.Printf("parsing query failed: %v\n", err)
+			http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+			return
+		}
+
+		if err := modify(req, parameters); err != nil {
+			log.Printf("modifying query parameters failed: %v\n", err)
+			http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+		}
+
+		req.URL.RawQuery = parameters.Encode()
+
+		next.ServeHTTP(rw, req)
+	})
+}
+
+func InjectUser(req *http.Request, parameters url.Values) error {
+	// To prevent SQL injections
+	parameters.Del("env")
+
+	session, ok := auth.GetSession(req)
+	if ok && !strings.ContainsAny(session.User, `\"':;`) {
+		log.Printf("Injecting user %s\n", session.User)
+		parameters.Set("env", "user:"+session.User)
+	}
+	return nil
+}