diff --git a/httpsso/handler_test.go b/httpsso/handler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1856ec59f5d05ad440eb7211fd03dab354f76509 --- /dev/null +++ b/httpsso/handler_test.go @@ -0,0 +1,130 @@ +package httpsso + +import ( + "crypto/tls" + "io" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/gorilla/mux" + "github.com/gorilla/securecookie" + "golang.org/x/crypto/ed25519" + + sso "git.autistici.org/id/go-sso" +) + +func newTestHTTPClient() *http.Client { + jar, _ := cookiejar.New(nil) + return &http.Client{ + Jar: jar, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + // This client will not follow redirects. + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } +} + +const ( + testHost = "service.example.com" + testService = "service.example.com/" + testDomain = "example.com" + testLoginServer = "login.example.com" +) + +func makeAuthRequest(t testing.TB, baseUri, path, service, domain string, priv []byte) []byte { + c := newTestHTTPClient() + resp, err := c.Get(baseUri + path) + if err != nil { + t.Fatalf("Get(%s): %v", path, err) + } + if resp.StatusCode != http.StatusFound { + t.Fatalf("Get(%s) expected 302, got %d", path, resp.StatusCode) + } + loc, err := url.Parse(resp.Header.Get("Location")) + if err != nil { + t.Fatalf("Get(%s) redirects to unparsable URL %s: %v", path, resp.Header.Get("Location"), err) + } + if loc.Host != testLoginServer { + t.Fatalf("Get(%s) got bad redirect: %s", path, loc) + } + resp.Body.Close() + + // Sign a ticket, pretending we are the SSO server, then make + // a new request to the sso_login endpoint. + signer, err := sso.NewSigner(priv) + if err != nil { + t.Fatal(err) + } + nonce := loc.Query().Get("n") + tkt := sso.NewTicket("user", service, domain, nonce, nil, 300*time.Second) + signed, err := signer.Sign(tkt) + if err != nil { + t.Fatal("Sign():", err) + } + u := make(url.Values) + destURL := "https://" + testHost + "/test" + u.Set("d", destURL) + u.Set("t", signed) + resp, err = c.Get(baseUri + "/sso_login?" + u.Encode()) + if err != nil { + t.Fatal("Get(/sso_login):", err) + } + if resp.StatusCode != http.StatusFound { + t.Fatalf("Get(/sso_login) expected 302, got %d", resp.StatusCode) + } + resp.Body.Close() + if s := resp.Header.Get("Location"); s != destURL { + t.Fatalf("Get(/sso_login) redirects to unexpected location %s", s) + } + + // Finally, requesting the original URL should work now. + resp, err = c.Get(baseUri + path) + if err != nil { + t.Fatalf("Get(%s, post-auth): %v", path, err) + } + if resp.StatusCode != 200 { + t.Fatalf("Get(%s, post-auth) expected 200, got %d", path, resp.StatusCode) + } + data, _ := ioutil.ReadAll(resp.Body) + resp.Body.Close() + return data +} + +func TestSSOWrapper(t *testing.T) { + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + + // Build a test app - note that we want to use a gorilla Mux + // here, otherwise cookie-based sessions won't work. + m := mux.NewRouter() + m.HandleFunc("/test", func(w http.ResponseWriter, _ *http.Request) { + io.WriteString(w, "OK") + }) + + w, err := NewSSOWrapper("https://"+testLoginServer+"/", pub, testDomain, securecookie.GenerateRandomKey(64), securecookie.GenerateRandomKey(32)) + if err != nil { + t.Fatal("NewSSOWrapper():", err) + } + + // Start a local test https server. + srv := httptest.NewTLSServer(w.Wrap(m, testService, nil)) + defer srv.Close() + + // Request a sample URL. + data := string(makeAuthRequest(t, srv.URL, "/test", testService, testDomain, priv)) + if data != "OK" { + t.Fatalf("Get() returned bad data: %s", data) + } +}