controlclient, ipn: adjust tests for authURL semantic changes

The tests cheat at filling out web forms by directly POSTing to
the target. The target for authURLs has changed slightly, the base
authURL now redirects the user to the login page.

Additionally, the authURL cycle now checks the cookie is set
correctly, so we add cookie jars where necessary to pass the
cookie through.
This commit is contained in:
David Crawshaw 2020-03-30 15:44:08 +11:00
parent a4ef345737
commit 0590ad68be
3 changed files with 114 additions and 35 deletions

View File

@ -7,12 +7,14 @@
package controlclient
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"os"
@ -310,25 +312,7 @@ func TestControl(t *testing.T) {
c1.Login(nil, LoginInteractive)
status := c1.waitStatus(t, stateURLVisitRequired)
authURL := status.New.URL
resp, err := c1.httpc.Get(authURL)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Errorf("GET %s failed: %q", authURL, resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Fatal(err)
}
cookies := resp.Cookies()
if len(cookies) == 0 || cookies[0].Name != "tailcontrol" {
t.Logf("GET %s: %s", authURL, string(body))
t.Fatalf("GET %s: bad cookie: %v", authURL, cookies)
}
c1.postAuthURL(t, "testuser1@tailscale.com", status.New)
c1.waitStatus(t, stateAuthenticated)
status = c1.waitStatus(t, stateSynchronized)
if status.New.Err != "" {
@ -443,8 +427,8 @@ func TestLoginInterrupt(t *testing.T) {
t.Errorf("auth URLs match for subsequent logins: %s", authURL)
}
form := url.Values{"user": []string{loginName}}
req, err := http.NewRequest("POST", authURL2, strings.NewReader(form.Encode()))
// Direct auth URL visit is not enough because our cookie is no longer fresh.
req, err := http.NewRequest("GET", authURL2, nil)
if err != nil {
t.Fatal(err)
}
@ -453,10 +437,37 @@ func TestLoginInterrupt(t *testing.T) {
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if i := bytes.Index(b, []byte("<body")); i != -1 { // strip off noisy <style> header
b = b[i:]
}
if !bytes.Contains(b, []byte("<form")) {
t.Errorf("missing login page: %s", b)
}
form := url.Values{"user": []string{loginName}}
req, err = http.NewRequest("POST", authURLForPOST(authURL2), strings.NewReader(form.Encode()))
if err != nil {
t.Fatal(err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err = c.httpc.Do(req.WithContext(c.ctx))
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatalf("POST %s failed: %q", authURL2, resp.Status)
}
cookies := resp.Cookies()
cookieURL, err := url.Parse(authURL)
if err != nil {
t.Fatal(err)
}
cookies := c.httpc.Jar.Cookies(cookieURL)
if len(cookies) == 0 || cookies[0].Name != "tailcontrol" {
t.Fatalf("POST %s: bad cookie: %v", authURL2, cookies)
}
@ -746,6 +757,7 @@ func TestNewUserWebFlow(t *testing.T) {
}
loginWith := "testuser1@tailscale.com"
authURL = authURLForPOST(authURL)
resp, err = c.httpc.PostForm(authURL, url.Values{"user": []string{loginWith}})
if err != nil {
t.Fatal(err)
@ -757,6 +769,9 @@ func TestNewUserWebFlow(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if i := bytes.Index(b, []byte("<body")); i != -1 { // strip off noisy <style> header
b = b[i:]
}
got = string(b)
if !strings.Contains(got, "This is a new machine") {
t.Fatalf("no machine authorization message:\n\n%s", got)
@ -830,11 +845,15 @@ func TestGoogleSigninButton(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if i := bytes.Index(b, []byte("<body")); i != -1 { // strip off noisy <style> header
b = b[i:]
}
got := string(b)
if !strings.Contains(got, `Sign in with Google`) {
t.Fatalf("page does not mention google signin button:\n\n%s", got)
}
authURL = authURLForPOST(authURL)
resp, err = c.httpc.PostForm(authURL, url.Values{"provider": []string{"google"}})
if err != nil {
t.Fatal(err)
@ -846,6 +865,9 @@ func TestGoogleSigninButton(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if i := bytes.Index(b, []byte("<body")); i != -1 { // strip off noisy <style> header
b = b[i:]
}
got = string(b)
if !strings.Contains(got, "Authorization successful") {
t.Fatalf("no machine authorization message:\n\n%s", got)
@ -990,6 +1012,11 @@ func (s *server) newClient(t *testing.T, name string) *client {
ch := make(chan statusChange, 1024)
httpc := s.http.Client()
var err error
httpc.Jar, err = cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
hi := NewHostinfo()
hi.FrontendLogID = "go-test-only"
hi.BackendLogID = "go-test-only"
@ -1063,9 +1090,18 @@ func (c *client) postAuthURL(t *testing.T, user string, status Status) *http.Coo
return postAuthURL(t, c.ctx, c.httpc, user, authURL)
}
func authURLForPOST(authURL string) string {
i := strings.Index(authURL, "/a/")
if i == -1 {
panic("bad authURL: " + authURL)
}
return authURL[:i] + "/login?refresh=true&next_url=" + url.PathEscape(authURL[i:])
}
func postAuthURL(t *testing.T, ctx context.Context, httpc *http.Client, user string, authURL string) *http.Cookie {
t.Helper()
authURL = authURLForPOST(authURL)
form := url.Values{"user": []string{user}}
req, err := http.NewRequest("POST", authURL, strings.NewReader(form.Encode()))
if err != nil {
@ -1076,12 +1112,21 @@ func postAuthURL(t *testing.T, ctx context.Context, httpc *http.Client, user str
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatalf("POST %s failed: %q", authURL, resp.Status)
b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if i := bytes.Index(b, []byte("<body")); i != -1 { // strip off noisy <style> header
b = b[i:]
}
cookies := resp.Cookies()
if resp.StatusCode != 200 {
t.Fatalf("POST %s failed: %q, body: %s", authURL, resp.Status, b)
}
cookieURL, err := url.Parse(authURL)
if err != nil {
t.Fatal(err)
}
cookies := httpc.Jar.Cookies(cookieURL)
if len(cookies) == 0 || cookies[0].Name != "tailcontrol" {
t.Fatalf("POST %s: bad cookie: %v", authURL, cookies)
t.Fatalf("POST %s: bad cookie: %v, body: %s", authURL, cookies, b)
}
return cookies[0]
}

View File

@ -10,6 +10,7 @@ import (
"context"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"os"
"testing"
@ -30,6 +31,11 @@ func TestClientsReusingKeys(t *testing.T) {
httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server.ServeHTTP(w, r)
}))
httpc := httpsrv.Client()
httpc.Jar, err = cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
server, err = control.New(tmpdir, tmpdir, httpsrv.URL, true)
if err != nil {
t.Fatal(err)
@ -64,7 +70,7 @@ func TestClientsReusingKeys(t *testing.T) {
t.Fatal(err)
}
const user = "testuser1@tailscale.onmicrosoft.com"
postAuthURL(t, ctx, httpsrv.Client(), user, authURL)
postAuthURL(t, ctx, httpc, user, authURL)
newURL, err := c1.WaitLoginURL(ctx, authURL)
if err != nil {
t.Fatal(err)
@ -132,6 +138,11 @@ func TestClientsReusingOldKey(t *testing.T) {
httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server.ServeHTTP(w, r)
}))
httpc := httpsrv.Client()
httpc.Jar, err = cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
server, err = control.New(tmpdir, tmpdir, httpsrv.URL, true)
if err != nil {
t.Fatal(err)
@ -149,7 +160,7 @@ func TestClientsReusingOldKey(t *testing.T) {
genOpts := func() Options {
return Options{
ServerURL: httpsrv.URL,
HTTPC: httpsrv.Client(),
HTTPC: httpc,
//TimeNow: s.control.TimeNow,
Logf: func(fmt string, args ...interface{}) {
t.Helper()
@ -171,7 +182,7 @@ func TestClientsReusingOldKey(t *testing.T) {
t.Fatal(err)
}
const user = "testuser1@tailscale.onmicrosoft.com"
postAuthURL(t, ctx, httpsrv.Client(), user, authURL)
postAuthURL(t, ctx, httpc, user, authURL)
newURL, err := c1.WaitLoginURL(ctx, authURL)
if err != nil {
t.Fatal(err)
@ -212,7 +223,7 @@ func TestClientsReusingOldKey(t *testing.T) {
} else if authURL == "" {
t.Fatal("expected authURL for reused oldNodeKey, got none")
} else {
postAuthURL(t, ctx, httpsrv.Client(), user, authURL)
postAuthURL(t, ctx, httpc, user, authURL)
if newURL, err := c1.WaitLoginURL(ctx, authURL); err != nil {
t.Fatal(err)
} else if newURL != "" {
@ -245,7 +256,7 @@ func TestClientsReusingOldKey(t *testing.T) {
} else if authURL == "" {
t.Fatal("expected authURL for reused oldNodeKey, got none")
} else {
postAuthURL(t, ctx, httpsrv.Client(), user, authURL)
postAuthURL(t, ctx, httpc, user, authURL)
if newURL, err := c1.WaitLoginURL(ctx, authURL); err != nil {
t.Fatal(err)
} else if newURL != "" {
@ -287,7 +298,7 @@ func TestClientsReusingOldKey(t *testing.T) {
} else if authURL == "" {
t.Fatal("expected authURL for reused nodeKey, got none")
} else {
postAuthURL(t, ctx, httpsrv.Client(), user, authURL)
postAuthURL(t, ctx, httpc, user, authURL)
if newURL, err := c1.WaitLoginURL(ctx, authURL); err != nil {
t.Fatal(err)
} else if newURL != "" {

View File

@ -10,7 +10,10 @@ import (
"bytes"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
@ -177,6 +180,13 @@ func newNode(t *testing.T, prefix string, https *httptest.Server) testNode {
t.Logf(prefix+": "+fmt, args...)
}
var err error
httpc := https.Client()
httpc.Jar, err = cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
tun := tuntest.NewChannelTUN()
e1, err := wgengine.NewUserspaceEngineAdvanced(logfe, tun.TUN(), wgengine.NewFakeRouter, 0)
if err != nil {
@ -200,10 +210,23 @@ func newNode(t *testing.T, prefix string, https *httptest.Server) testNode {
Notify: func(n Notify) {
// Automatically visit auth URLs
if n.BrowseToURL != nil {
t.Logf("\n\n\nURL! %vv\n", *n.BrowseToURL)
hc := https.Client()
_, err := hc.Get(*n.BrowseToURL)
t.Logf("BrowseToURL: %v", *n.BrowseToURL)
authURL := *n.BrowseToURL
i := strings.Index(authURL, "/a/")
if i == -1 {
panic("bad authURL: " + authURL)
}
authURL = authURL[:i] + "/login?refresh=true&next_url=" + url.PathEscape(authURL[i:])
form := url.Values{"user": []string{c.LoginName}}
req, err := http.NewRequest("POST", authURL, strings.NewReader(form.Encode()))
if err != nil {
t.Fatal(err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if _, err := httpc.Do(req); err != nil {
t.Logf("BrowseToURL: %v\n", err)
}
}