From dbba63f98e3698deb51dc0c091b3bc413497f64d Mon Sep 17 00:00:00 2001 From: "luke.hopkins" Date: Fri, 17 Nov 2017 17:33:55 +0000 Subject: [PATCH] preliminary implemntation of redirect logic --- login/config.go | 74 +++++++++++++++++++++---------------- login/handler.go | 96 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 133 insertions(+), 37 deletions(-) diff --git a/login/config.go b/login/config.go index bd7fb47..6732faf 100644 --- a/login/config.go +++ b/login/config.go @@ -23,20 +23,23 @@ func init() { // DefaultConfig for the loginsrv handler func DefaultConfig() *Config { return &Config{ - Host: "localhost", - Port: "6789", - LogLevel: "info", - JwtSecret: jwtDefaultSecret, - JwtExpiry: 24 * time.Hour, - JwtRefreshes: 0, - SuccessURL: "/", - LogoutURL: "", - LoginPath: "/login", - CookieName: "jwt_token", - CookieHTTPOnly: true, - Backends: Options{}, - Oauth: Options{}, - GracePeriod: 5 * time.Second, + Host: "localhost", + Port: "6789", + LogLevel: "info", + JwtSecret: jwtDefaultSecret, + JwtExpiry: 24 * time.Hour, + JwtRefreshes: 0, + SuccessURL: "/", + AllowRedirects: true, + PreventExternalRedirects: true, + CheckRefererOnRedirects: true, + LogoutURL: "", + LoginPath: "/login", + CookieName: "jwt_token", + CookieHTTPOnly: true, + Backends: Options{}, + Oauth: Options{}, + GracePeriod: 5 * time.Second, } } @@ -44,24 +47,27 @@ const envPrefix = "LOGINSRV_" // Config for the loginsrv handler type Config struct { - Host string - Port string - LogLevel string - TextLogging bool - JwtSecret string - JwtExpiry time.Duration - JwtRefreshes int - SuccessURL string - LogoutURL string - Template string - LoginPath string - CookieName string - CookieExpiry time.Duration - CookieDomain string - CookieHTTPOnly bool - Backends Options - Oauth Options - GracePeriod time.Duration + Host string + Port string + LogLevel string + TextLogging bool + JwtSecret string + JwtExpiry time.Duration + JwtRefreshes int + SuccessURL string + AllowRedirects bool + PreventExternalRedirects bool + CheckRefererOnRedirects bool + LogoutURL string + Template string + LoginPath string + CookieName string + CookieExpiry time.Duration + CookieDomain string + CookieHTTPOnly bool + Backends Options + Oauth Options + GracePeriod time.Duration } // Options is the configuration structure for oauth and backend provider @@ -104,6 +110,10 @@ func (c *Config) ConfigureFlagSet(f *flag.FlagSet) { f.DurationVar(&c.CookieExpiry, "cookie-expiry", c.CookieExpiry, "The expiry duration for the cookie, e.g. 2h or 3h30m. Default is browser session") f.StringVar(&c.CookieDomain, "cookie-domain", c.CookieDomain, "The optional domain parameter for the cookie") f.StringVar(&c.SuccessURL, "success-url", c.SuccessURL, "The url to redirect after login") + f.BoolVar(&c.AllowRedirects, "allow-redirects", c.AllowRedirects, "Allow dynamic redirects by parameter") + f.BoolVar(&c.PreventExternalRedirects, "prevent-external-redirects", c.PreventExternalRedirects, "Prevent dynamic redirects from redirecting to an external domain") + f.BoolVar(&c.CheckRefererOnRedirects, "check-referer-on-redirects", c.CheckRefererOnRedirects, "When redirecting check that the referer is the same domain") + f.StringVar(&c.LogoutURL, "logout-url", c.LogoutURL, "The url or path to redirect after logout") f.StringVar(&c.Template, "template", c.Template, "An alternative template for the login form") f.StringVar(&c.LoginPath, "login-path", c.LoginPath, "The path of the login resource") diff --git a/login/handler.go b/login/handler.go index 28ef13b..1faf78e 100644 --- a/login/handler.go +++ b/login/handler.go @@ -4,14 +4,17 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/davecgh/go-spew/spew" "github.com/dgrijalva/jwt-go" "github.com/tarent/loginsrv/logging" "github.com/tarent/loginsrv/model" "github.com/tarent/loginsrv/oauth2" - "io/ioutil" - "net/http" - "strings" - "time" ) const contentTypeHTML = "text/html; charset=utf-8" @@ -71,11 +74,34 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.handleOauth(w, r) return } + if h.shouldSetCookie(r) { + queries, _ := url.ParseQuery(r.URL.RawQuery) + cookie := http.Cookie{Name: "redirect_url", Value: queries.Get("backTo")} + http.SetCookie(w, &cookie) + } h.handleLogin(w, r) return } +func (h *Handler) shouldSetCookie(r *http.Request) bool { + if h.config.AllowRedirects { + if h.config.CheckRefererOnRedirects { + referer, _ := url.Parse(r.Header.Get("Referer")) + if referer.Host != r.Host { + logging.Application(r.Header).Warnf( + "Referer domain: '%s' does not match current domain '%s'", + referer.Host, + r.Host, + ) + return false + } + } + return true + } + return false +} + func (h *Handler) handleOauth(w http.ResponseWriter, r *http.Request) { startedFlow, authenticated, userInfo, err := h.oauth.Handle(w, r) @@ -231,7 +257,10 @@ func (h *Handler) respondAuthenticated(w http.ResponseWriter, r *http.Request, u http.SetCookie(w, cookie) - w.Header().Set("Location", h.config.SuccessURL) + //redirectURL := h.config.SuccessURL + //fmt.Printf("redirectURL is: %s\n", h.redirectURL(r)) + + w.Header().Set("Location", h.redirectURL(r)) w.WriteHeader(303) return } @@ -241,6 +270,63 @@ func (h *Handler) respondAuthenticated(w http.ResponseWriter, r *http.Request, u fmt.Fprintf(w, "%s", token) } +func (h *Handler) redirectURL(r *http.Request) string { + origin, _ := url.Parse(r.Header.Get("Origin")) + originScheme := origin.Scheme + originHost := origin.Host + redirectURL := fmt.Sprintf("%s://%s%s", originScheme, originHost, h.config.SuccessURL) + if h.config.AllowRedirects { + cookie, _ := r.Cookie("redirect_url") + parsedURL, err := url.Parse(cookie.Value) + if err != nil { + spew.Dump("oops can't parse that url") + } + scheme := parsedURL.Scheme + host := parsedURL.Host + path := parsedURL.Path + //spew.Dump(scheme) + //spew.Dump(host) + //spew.Dump(path) + + if scheme == "" { + scheme = originScheme + } + + if host == "" { + host = originHost + } else { + if h.config.PreventExternalRedirects { + if host != originHost { + logging.Application(r.Header).Warnf( + "Redirect attempt to '%s' but -prevent-external-redirects is set to true", + host, + ) + return redirectURL + } else { + host = originHost + } + } else { + //TODO read domains from file + domains := "2google.com" + if host != domains { + logging.Application(r.Header).Warnf( + "Redirect attempt to '%s' but it is not in domain whitelist", + host, + ) + return redirectURL + } + } + } + + if path == "" { + path = h.config.SuccessURL + } + + redirectURL = fmt.Sprintf("%s://%s%s", scheme, host, path) + } + return redirectURL +} + func (h *Handler) createToken(userInfo jwt.Claims) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS512, userInfo) return token.SignedString([]byte(h.config.JwtSecret))