2018-07-19 08:50:12 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-01-22 10:50:55 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
// "reflect"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/op/go-logging"
|
2018-07-19 08:50:12 +01:00
|
|
|
)
|
|
|
|
|
2018-12-10 12:44:13 +00:00
|
|
|
/**
|
|
|
|
* Utilities
|
|
|
|
*/
|
2018-07-19 08:50:12 +01:00
|
|
|
|
2019-01-22 10:50:55 +00:00
|
|
|
type TokenServerHandler struct{}
|
|
|
|
|
2018-07-19 08:50:12 +01:00
|
|
|
func (t *TokenServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2019-01-22 10:50:55 +00:00
|
|
|
fmt.Fprint(w, `{"access_token":"123456789"}`)
|
2018-07-19 08:50:12 +01:00
|
|
|
}
|
|
|
|
|
2019-01-22 10:50:55 +00:00
|
|
|
type UserServerHandler struct{}
|
|
|
|
|
2018-07-19 08:50:12 +01:00
|
|
|
func (t *UserServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2019-01-22 10:50:55 +00:00
|
|
|
fmt.Fprint(w, `{
|
2018-07-19 08:50:12 +01:00
|
|
|
"id":"1",
|
|
|
|
"email":"example@example.com",
|
|
|
|
"verified_email":true,
|
|
|
|
"hd":"example.com"
|
|
|
|
}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2019-01-22 10:50:55 +00:00
|
|
|
// Remove for debugging
|
|
|
|
logging.SetLevel(logging.INFO, "traefik-forward-auth")
|
2018-07-19 08:50:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func httpRequest(r *http.Request, c *http.Cookie) (*http.Response, string) {
|
2019-01-22 10:50:55 +00:00
|
|
|
w := httptest.NewRecorder()
|
2018-07-19 08:50:12 +01:00
|
|
|
|
2019-01-22 10:50:55 +00:00
|
|
|
// Set cookies on recorder
|
|
|
|
if c != nil {
|
|
|
|
http.SetCookie(w, c)
|
|
|
|
}
|
2018-07-19 08:50:12 +01:00
|
|
|
|
2019-01-22 10:50:55 +00:00
|
|
|
// Copy into request
|
|
|
|
for _, c := range w.HeaderMap["Set-Cookie"] {
|
|
|
|
r.Header.Add("Cookie", c)
|
|
|
|
}
|
2018-07-19 08:50:12 +01:00
|
|
|
|
2019-01-22 10:50:55 +00:00
|
|
|
handler(w, r)
|
2018-07-19 08:50:12 +01:00
|
|
|
|
2019-01-22 10:50:55 +00:00
|
|
|
res := w.Result()
|
|
|
|
body, _ := ioutil.ReadAll(res.Body)
|
2018-07-19 08:50:12 +01:00
|
|
|
|
2019-01-22 10:50:55 +00:00
|
|
|
return res, string(body)
|
2018-07-19 08:50:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func newHttpRequest(uri string) *http.Request {
|
2019-01-22 10:50:55 +00:00
|
|
|
r := httptest.NewRequest("", "http://example.com", nil)
|
|
|
|
r.Header.Add("X-Forwarded-Uri", uri)
|
|
|
|
return r
|
2018-07-19 08:50:12 +01:00
|
|
|
}
|
|
|
|
|
2018-10-29 17:39:36 +00:00
|
|
|
func qsDiff(one, two url.Values) {
|
2019-01-22 10:50:55 +00:00
|
|
|
for k, _ := range one {
|
|
|
|
if two.Get(k) == "" {
|
|
|
|
fmt.Printf("Key missing: %s\n", k)
|
|
|
|
}
|
|
|
|
if one.Get(k) != two.Get(k) {
|
|
|
|
fmt.Printf("Value different for %s: expected: '%s' got: '%s'\n", k, one.Get(k), two.Get(k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for k, _ := range two {
|
|
|
|
if one.Get(k) == "" {
|
|
|
|
fmt.Printf("Extra key: %s\n", k)
|
|
|
|
}
|
|
|
|
}
|
2018-10-29 17:39:36 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 12:44:13 +00:00
|
|
|
/**
|
|
|
|
* Tests
|
|
|
|
*/
|
|
|
|
|
2018-07-19 08:50:12 +01:00
|
|
|
func TestHandler(t *testing.T) {
|
2019-01-22 10:50:55 +00:00
|
|
|
fw = &ForwardAuth{
|
|
|
|
Path: "_oauth",
|
|
|
|
ClientId: "idtest",
|
|
|
|
ClientSecret: "sectest",
|
|
|
|
Scope: "scopetest",
|
|
|
|
LoginURL: &url.URL{
|
|
|
|
Scheme: "http",
|
|
|
|
Host: "test.com",
|
|
|
|
Path: "/auth",
|
|
|
|
},
|
|
|
|
CookieName: "cookie_test",
|
|
|
|
Lifetime: time.Second * time.Duration(10),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should redirect vanilla request to login url
|
|
|
|
req := newHttpRequest("foo")
|
|
|
|
res, _ := httpRequest(req, nil)
|
|
|
|
if res.StatusCode != 307 {
|
|
|
|
t.Error("Vanilla request should be redirected with 307, got:", res.StatusCode)
|
|
|
|
}
|
|
|
|
fwd, _ := res.Location()
|
|
|
|
if fwd.Scheme != "http" || fwd.Host != "test.com" || fwd.Path != "/auth" {
|
|
|
|
t.Error("Vanilla request should be redirected to login url, got:", fwd)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should catch invalid cookie
|
|
|
|
req = newHttpRequest("foo")
|
|
|
|
|
|
|
|
c := fw.MakeCookie(req, "test@example.com")
|
|
|
|
parts := strings.Split(c.Value, "|")
|
|
|
|
c.Value = fmt.Sprintf("bad|%s|%s", parts[1], parts[2])
|
|
|
|
|
|
|
|
res, _ = httpRequest(req, c)
|
|
|
|
if res.StatusCode != 401 {
|
|
|
|
t.Error("Request with invalid cookie shound't be authorised", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should validate email
|
|
|
|
req = newHttpRequest("foo")
|
|
|
|
|
|
|
|
c = fw.MakeCookie(req, "test@example.com")
|
|
|
|
fw.Domain = []string{"test.com"}
|
|
|
|
|
|
|
|
res, _ = httpRequest(req, c)
|
|
|
|
if res.StatusCode != 401 {
|
|
|
|
t.Error("Request with invalid cookie shound't be authorised", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should allow valid request email
|
|
|
|
req = newHttpRequest("foo")
|
|
|
|
|
|
|
|
c = fw.MakeCookie(req, "test@example.com")
|
|
|
|
fw.Domain = []string{}
|
|
|
|
|
|
|
|
res, _ = httpRequest(req, c)
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
t.Error("Valid request should be allowed, got:", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should pass through user
|
|
|
|
users := res.Header["X-Forwarded-User"]
|
|
|
|
if len(users) != 1 {
|
|
|
|
t.Error("Valid request missing X-Forwarded-User header")
|
|
|
|
} else if users[0] != "test@example.com" {
|
|
|
|
t.Error("X-Forwarded-User should match user, got: ", users)
|
|
|
|
}
|
2018-07-19 08:50:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCallback(t *testing.T) {
|
2019-01-22 10:50:55 +00:00
|
|
|
fw = &ForwardAuth{
|
|
|
|
Path: "_oauth",
|
|
|
|
ClientId: "idtest",
|
|
|
|
ClientSecret: "sectest",
|
|
|
|
Scope: "scopetest",
|
|
|
|
LoginURL: &url.URL{
|
|
|
|
Scheme: "http",
|
|
|
|
Host: "test.com",
|
|
|
|
Path: "/auth",
|
|
|
|
},
|
|
|
|
CSRFCookieName: "csrf_test",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup token server
|
|
|
|
tokenServerHandler := &TokenServerHandler{}
|
|
|
|
tokenServer := httptest.NewServer(tokenServerHandler)
|
|
|
|
defer tokenServer.Close()
|
|
|
|
tokenUrl, _ := url.Parse(tokenServer.URL)
|
|
|
|
fw.TokenURL = tokenUrl
|
|
|
|
|
|
|
|
// Setup user server
|
|
|
|
userServerHandler := &UserServerHandler{}
|
|
|
|
userServer := httptest.NewServer(userServerHandler)
|
|
|
|
defer userServer.Close()
|
|
|
|
userUrl, _ := url.Parse(userServer.URL)
|
|
|
|
fw.UserURL = userUrl
|
|
|
|
|
|
|
|
// Should pass auth response request to callback
|
|
|
|
req := newHttpRequest("_oauth")
|
|
|
|
res, _ := httpRequest(req, nil)
|
|
|
|
if res.StatusCode != 401 {
|
|
|
|
t.Error("Auth callback without cookie shound't be authorised, got:", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should catch invalid csrf cookie
|
|
|
|
req = newHttpRequest("_oauth?state=12345678901234567890123456789012:http://redirect")
|
|
|
|
c := fw.MakeCSRFCookie(req, "nononononononononononononononono")
|
|
|
|
res, _ = httpRequest(req, c)
|
|
|
|
if res.StatusCode != 401 {
|
|
|
|
t.Error("Auth callback with invalid cookie shound't be authorised, got:", res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should redirect valid request
|
|
|
|
req = newHttpRequest("_oauth?state=12345678901234567890123456789012:http://redirect")
|
|
|
|
c = fw.MakeCSRFCookie(req, "12345678901234567890123456789012")
|
|
|
|
res, _ = httpRequest(req, c)
|
|
|
|
if res.StatusCode != 307 {
|
|
|
|
t.Error("Valid callback should be allowed, got:", res.StatusCode)
|
|
|
|
}
|
|
|
|
fwd, _ := res.Location()
|
|
|
|
if fwd.Scheme != "http" || fwd.Host != "redirect" || fwd.Path != "" {
|
|
|
|
t.Error("Valid request should be redirected to return url, got:", fwd)
|
|
|
|
}
|
|
|
|
}
|