Skip to content
Snippets Groups Projects

Refactor the login handler

Merged ale requested to merge better-login into master
2 files
+ 160
24
Compare changes
  • Side-by-side
  • Inline
Files
2
+ 55
24
@@ -7,6 +7,7 @@ import (
"errors"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
@@ -68,12 +69,23 @@ func startTestHTTPServerWithKeyStore(t testing.TB) (string, *httptest.Server) {
return tmpdir, createTestHTTPServer(t, config)
}
func newTestHTTPClient() *http.Client {
func makeHTTPClient(dnsOverrides map[string]string, followExternalRedirects bool) *http.Client {
jar, _ := cookiejar.New(nil)
var dialfn func(ctx context.Context, network, addr string) (net.Conn, error)
if dnsOverrides != nil {
dialer := new(net.Dialer)
dialfn = func(ctx context.Context, network, addr string) (net.Conn, error) {
if override, ok := dnsOverrides[addr]; ok {
addr = override
}
return dialer.DialContext(ctx, network, addr)
}
}
transport := NewLoggedTransport(&http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DialContext: dialfn,
}, DefaultLogger{Dump: true})
return &http.Client{
Jar: jar,
@@ -83,7 +95,7 @@ func newTestHTTPClient() *http.Client {
if len(via) > 10 {
return errors.New("too many redirects")
}
if !strings.HasPrefix(req.URL.Host, "127.0.0.1:") {
if !followExternalRedirects && !strings.HasPrefix(req.URL.Host, "127.0.0.1:") {
return http.ErrUseLastResponse
}
return nil
@@ -91,6 +103,10 @@ func newTestHTTPClient() *http.Client {
}
}
func newTestHTTPClient() *http.Client {
return makeHTTPClient(nil, false)
}
func TestHTTP_ServeStaticAsset(t *testing.T) {
tmpdir, httpSrv := startTestHTTPServer(t)
defer os.RemoveAll(tmpdir)
@@ -106,10 +122,10 @@ func TestHTTP_ServeStaticAsset(t *testing.T) {
}
}
func doGet(t testing.TB, srv *httptest.Server, c *http.Client, relativeURL string, checkResponse ...func(testing.TB, *http.Response)) {
resp, err := c.Get(srv.URL + relativeURL)
func doGet(t testing.TB, c *http.Client, uri string, checkResponse ...func(testing.TB, *http.Response)) {
resp, err := c.Get(uri)
if err != nil {
t.Fatalf("http.Get(%s): %v", relativeURL, err)
t.Fatalf("http.Get(%s): %v", uri, err)
}
defer resp.Body.Close()
for _, f := range checkResponse {
@@ -117,10 +133,10 @@ func doGet(t testing.TB, srv *httptest.Server, c *http.Client, relativeURL strin
}
}
func doPostForm(t testing.TB, srv *httptest.Server, c *http.Client, relativeURL string, v url.Values, checkResponse ...func(testing.TB, *http.Response)) {
resp, err := c.PostForm(srv.URL+relativeURL, v)
func doPostForm(t testing.TB, c *http.Client, uri string, v url.Values, checkResponse ...func(testing.TB, *http.Response)) {
resp, err := c.PostForm(uri, v)
if err != nil {
t.Fatalf("http.Get(%s): %v", relativeURL, err)
t.Fatalf("http.Get(%s): %v", uri, err)
}
defer resp.Body.Close()
for _, f := range checkResponse {
@@ -173,6 +189,21 @@ func checkLoginOTPPage(t testing.TB, resp *http.Response) {
}
}
func checkLogoutPage(t testing.TB, resp *http.Response) {
if resp.Request.URL.Path != "/logout" {
t.Errorf("request path is not /logout (%s)", resp.Request.URL.String())
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("reading body: %v", err)
}
if s := string(data); !strings.Contains(s, "Signing you out from all services") {
t.Fatalf("not the logout page:\n%s", s)
}
}
func TestHTTP_Login(t *testing.T) {
tmpdir, httpSrv := startTestHTTPServer(t)
defer os.RemoveAll(tmpdir)
@@ -186,14 +217,14 @@ func TestHTTP_Login(t *testing.T) {
v.Set("s", "service.example.com/")
v.Set("d", "https://service.example.com/admin/")
v.Set("n", "averysecretnonce")
doGet(t, httpSrv, c, "/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
doGet(t, c, httpSrv.URL+"/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
// Attempt to login by submitting the form. We expect the
// result to be a 302 redirect to the target service.
v = make(url.Values)
v.Set("username", "testuser")
v.Set("password", "password")
doPostForm(t, httpSrv, c, "/login", v, checkRedirectToTargetService)
doPostForm(t, c, httpSrv.URL+"/login", v, checkRedirectToTargetService)
}
func TestHTTP_LoginOnSecondAttempt(t *testing.T) {
@@ -209,20 +240,20 @@ func TestHTTP_LoginOnSecondAttempt(t *testing.T) {
v.Set("s", "service.example.com/")
v.Set("d", "https://service.example.com/admin/")
v.Set("n", "averysecretnonce")
doGet(t, httpSrv, c, "/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
doGet(t, c, httpSrv.URL+"/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
// Attempt to login with wrong credentials.
v = make(url.Values)
v.Set("username", "testuser")
v.Set("password", "badpassword")
doPostForm(t, httpSrv, c, "/login", v, checkStatusOk, checkLoginPasswordPage)
doPostForm(t, c, httpSrv.URL+"/login", v, checkStatusOk, checkLoginPasswordPage)
// Attempt to login by submitting the form. We expect the
// result to be a 302 redirect to the target service.
v = make(url.Values)
v.Set("username", "testuser")
v.Set("password", "password")
doPostForm(t, httpSrv, c, "/login", v, checkRedirectToTargetService)
doPostForm(t, c, httpSrv.URL+"/login", v, checkRedirectToTargetService)
}
func TestHTTP_LoginAndLogout(t *testing.T) {
@@ -238,24 +269,24 @@ func TestHTTP_LoginAndLogout(t *testing.T) {
v.Set("s", "service.example.com/")
v.Set("d", "https://service.example.com/admin/")
v.Set("n", "averysecretnonce")
doGet(t, httpSrv, c, "/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
doGet(t, c, httpSrv.URL+"/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
// Attempt to login by submitting the form. We expect the
// result to be a 302 redirect to the target service.
v = make(url.Values)
v.Set("username", "testuser")
v.Set("password", "password")
doPostForm(t, httpSrv, c, "/login", v, checkRedirectToTargetService)
doPostForm(t, c, httpSrv.URL+"/login", v, checkRedirectToTargetService)
// Make a logout request.
doGet(t, httpSrv, c, "/logout", checkStatusOk)
doGet(t, c, httpSrv.URL+"/logout", checkStatusOk, checkLogoutPage)
// This new authorization request should send us to the login page.
v = make(url.Values)
v.Set("s", "service.example.com/")
v.Set("d", "https://service.example.com/admin/")
v.Set("n", "averysecretnonce")
doGet(t, httpSrv, c, "/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
doGet(t, c, httpSrv.URL+"/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
}
func TestHTTP_LoginOTP(t *testing.T) {
@@ -271,19 +302,19 @@ func TestHTTP_LoginOTP(t *testing.T) {
v.Set("s", "service.example.com/")
v.Set("d", "https://service.example.com/admin/")
v.Set("n", "averysecretnonce")
doGet(t, httpSrv, c, "/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
doGet(t, c, httpSrv.URL+"/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
// Attempt to login by submitting the form. We should see the OTP page.
v = make(url.Values)
v.Set("username", "test2fa")
v.Set("password", "password")
doPostForm(t, httpSrv, c, "/login", v, checkStatusOk, checkLoginOTPPage)
doPostForm(t, c, httpSrv.URL+"/login", v, checkStatusOk, checkLoginOTPPage)
// Submit the correct OTP token. We expect the result to be a
// 302 redirect to the target service.
v = make(url.Values)
v.Set("otp", "123456")
doPostForm(t, httpSrv, c, "/login/otp", v, checkRedirectToTargetService)
doPostForm(t, c, httpSrv.URL+"/login/otp", v, checkRedirectToTargetService)
}
func createFakeKeyStore(t testing.TB, username, password string) *httptest.Server {
@@ -322,14 +353,14 @@ func TestHTTP_LoginWithKeyStore(t *testing.T) {
v.Set("s", "service.example.com/")
v.Set("d", "https://service.example.com/admin/")
v.Set("n", "averysecretnonce")
doGet(t, httpSrv, c, "/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
doGet(t, c, httpSrv.URL+"/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
// Attempt to login by submitting the form. We expect the
// result to be a 302 redirect to the target service.
v = make(url.Values)
v.Set("username", "testuser")
v.Set("password", "password")
doPostForm(t, httpSrv, c, "/login", v, checkRedirectToTargetService)
doPostForm(t, c, httpSrv.URL+"/login", v, checkRedirectToTargetService)
}
func TestHTTP_CORS(t *testing.T) {
@@ -346,14 +377,14 @@ func TestHTTP_CORS(t *testing.T) {
v.Set("s", "service.example.com/")
v.Set("d", "https://service.example.com/admin/")
v.Set("n", "averysecretnonce")
doGet(t, httpSrv, c, "/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
doGet(t, c, httpSrv.URL+"/?"+v.Encode(), checkStatusOk, checkLoginPasswordPage)
// Attempt to login by submitting the form. We expect the
// result to be a 302 redirect to the target service.
v = make(url.Values)
v.Set("username", "testuser")
v.Set("password", "password")
doPostForm(t, httpSrv, c, "/login", v, checkRedirectToTargetService)
doPostForm(t, c, httpSrv.URL+"/login", v, checkRedirectToTargetService)
// Simulate a CORS preflight request.
v = make(url.Values)
Loading