|
|
@@ -11,7 +11,6 @@ import (
|
|
|
"net/http"
|
|
|
"net/http/httptest"
|
|
|
"net/url"
|
|
|
- "reflect"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
"time"
|
|
|
@@ -19,7 +18,6 @@ import (
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
"tailscale.com/client/tailscale"
|
|
|
"tailscale.com/client/tailscale/apitype"
|
|
|
- "tailscale.com/ipn"
|
|
|
"tailscale.com/ipn/ipnstate"
|
|
|
"tailscale.com/net/memnet"
|
|
|
"tailscale.com/tailcfg"
|
|
|
@@ -412,9 +410,14 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
defer localapi.Close()
|
|
|
go localapi.Serve(lal)
|
|
|
|
|
|
+ timeNow := time.Now()
|
|
|
+ oneHourAgo := timeNow.Add(-time.Hour)
|
|
|
+ sixtyDaysAgo := timeNow.Add(-sessionCookieExpiry * 2)
|
|
|
+
|
|
|
s := &Server{
|
|
|
lc: &tailscale.LocalClient{Dial: lal.Dial},
|
|
|
tsDebugMode: "full",
|
|
|
+ timeNow: func() time.Time { return timeNow },
|
|
|
}
|
|
|
|
|
|
successCookie := "ts-cookie-success"
|
|
|
@@ -422,7 +425,8 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
ID: successCookie,
|
|
|
SrcNode: remoteNode.Node.ID,
|
|
|
SrcUser: user.ID,
|
|
|
- Created: time.Now(),
|
|
|
+ Created: oneHourAgo,
|
|
|
+ AuthID: testAuthPathSuccess,
|
|
|
AuthURL: testControlURL + testAuthPathSuccess,
|
|
|
})
|
|
|
failureCookie := "ts-cookie-failure"
|
|
|
@@ -430,7 +434,8 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
ID: failureCookie,
|
|
|
SrcNode: remoteNode.Node.ID,
|
|
|
SrcUser: user.ID,
|
|
|
- Created: time.Now(),
|
|
|
+ Created: oneHourAgo,
|
|
|
+ AuthID: testAuthPathError,
|
|
|
AuthURL: testControlURL + testAuthPathError,
|
|
|
})
|
|
|
expiredCookie := "ts-cookie-expired"
|
|
|
@@ -438,7 +443,8 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
ID: expiredCookie,
|
|
|
SrcNode: remoteNode.Node.ID,
|
|
|
SrcUser: user.ID,
|
|
|
- Created: time.Now().Add(-sessionCookieExpiry * 2),
|
|
|
+ Created: sixtyDaysAgo,
|
|
|
+ AuthID: "/a/old-auth-url",
|
|
|
AuthURL: testControlURL + "/a/old-auth-url",
|
|
|
})
|
|
|
|
|
|
@@ -448,19 +454,40 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
query string
|
|
|
wantStatus int
|
|
|
wantResp *authResponse
|
|
|
- wantNewCookie bool // new cookie generated
|
|
|
+ wantNewCookie bool // new cookie generated
|
|
|
+ wantSession *browserSession // session associated w/ cookie at end of request
|
|
|
}{
|
|
|
{
|
|
|
name: "new-session-created",
|
|
|
wantStatus: http.StatusOK,
|
|
|
wantResp: &authResponse{OK: false, AuthURL: testControlURL + testAuthPath},
|
|
|
wantNewCookie: true,
|
|
|
- }, {
|
|
|
+ wantSession: &browserSession{
|
|
|
+ ID: "GENERATED_ID", // gets swapped for newly created ID by test
|
|
|
+ SrcNode: remoteNode.Node.ID,
|
|
|
+ SrcUser: user.ID,
|
|
|
+ Created: timeNow,
|
|
|
+ AuthID: testAuthPath,
|
|
|
+ AuthURL: testControlURL + testAuthPath,
|
|
|
+ Authenticated: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
name: "query-existing-incomplete-session",
|
|
|
cookie: successCookie,
|
|
|
wantStatus: http.StatusOK,
|
|
|
wantResp: &authResponse{OK: false, AuthURL: testControlURL + testAuthPathSuccess},
|
|
|
- }, {
|
|
|
+ wantSession: &browserSession{
|
|
|
+ ID: successCookie,
|
|
|
+ SrcNode: remoteNode.Node.ID,
|
|
|
+ SrcUser: user.ID,
|
|
|
+ Created: oneHourAgo,
|
|
|
+ AuthID: testAuthPathSuccess,
|
|
|
+ AuthURL: testControlURL + testAuthPathSuccess,
|
|
|
+ Authenticated: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
name: "transition-to-successful-session",
|
|
|
cookie: successCookie,
|
|
|
// query "wait" indicates the FE wants to make
|
|
|
@@ -468,29 +495,70 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
query: "wait=true",
|
|
|
wantStatus: http.StatusOK,
|
|
|
wantResp: &authResponse{OK: true},
|
|
|
- }, {
|
|
|
+ wantSession: &browserSession{
|
|
|
+ ID: successCookie,
|
|
|
+ SrcNode: remoteNode.Node.ID,
|
|
|
+ SrcUser: user.ID,
|
|
|
+ Created: oneHourAgo,
|
|
|
+ AuthID: testAuthPathSuccess,
|
|
|
+ AuthURL: testControlURL + testAuthPathSuccess,
|
|
|
+ Authenticated: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
name: "query-existing-complete-session",
|
|
|
cookie: successCookie,
|
|
|
wantStatus: http.StatusOK,
|
|
|
wantResp: &authResponse{OK: true},
|
|
|
- }, {
|
|
|
- name: "transition-to-failed-session",
|
|
|
- cookie: failureCookie,
|
|
|
- query: "wait=true",
|
|
|
- wantStatus: http.StatusUnauthorized,
|
|
|
- wantResp: nil,
|
|
|
- }, {
|
|
|
+ wantSession: &browserSession{
|
|
|
+ ID: successCookie,
|
|
|
+ SrcNode: remoteNode.Node.ID,
|
|
|
+ SrcUser: user.ID,
|
|
|
+ Created: oneHourAgo,
|
|
|
+ AuthID: testAuthPathSuccess,
|
|
|
+ AuthURL: testControlURL + testAuthPathSuccess,
|
|
|
+ Authenticated: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "transition-to-failed-session",
|
|
|
+ cookie: failureCookie,
|
|
|
+ query: "wait=true",
|
|
|
+ wantStatus: http.StatusUnauthorized,
|
|
|
+ wantResp: nil,
|
|
|
+ wantSession: nil, // session deleted
|
|
|
+ },
|
|
|
+ {
|
|
|
name: "failed-session-cleaned-up",
|
|
|
cookie: failureCookie,
|
|
|
wantStatus: http.StatusOK,
|
|
|
wantResp: &authResponse{OK: false, AuthURL: testControlURL + testAuthPath},
|
|
|
wantNewCookie: true,
|
|
|
- }, {
|
|
|
+ wantSession: &browserSession{
|
|
|
+ ID: "GENERATED_ID",
|
|
|
+ SrcNode: remoteNode.Node.ID,
|
|
|
+ SrcUser: user.ID,
|
|
|
+ Created: timeNow,
|
|
|
+ AuthID: testAuthPath,
|
|
|
+ AuthURL: testControlURL + testAuthPath,
|
|
|
+ Authenticated: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
name: "expired-cookie-gets-new-session",
|
|
|
cookie: expiredCookie,
|
|
|
wantStatus: http.StatusOK,
|
|
|
wantResp: &authResponse{OK: false, AuthURL: testControlURL + testAuthPath},
|
|
|
wantNewCookie: true,
|
|
|
+ wantSession: &browserSession{
|
|
|
+ ID: "GENERATED_ID",
|
|
|
+ SrcNode: remoteNode.Node.ID,
|
|
|
+ SrcUser: user.ID,
|
|
|
+ Created: timeNow,
|
|
|
+ AuthID: testAuthPath,
|
|
|
+ AuthURL: testControlURL + testAuthPath,
|
|
|
+ Authenticated: false,
|
|
|
+ },
|
|
|
},
|
|
|
}
|
|
|
for _, tt := range tests {
|
|
|
@@ -503,6 +571,8 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
s.serveTailscaleAuth(w, r)
|
|
|
res := w.Result()
|
|
|
defer res.Body.Close()
|
|
|
+
|
|
|
+ // Validate response status/data.
|
|
|
if gotStatus := res.StatusCode; tt.wantStatus != gotStatus {
|
|
|
t.Errorf("wrong status; want=%v, got=%v", tt.wantStatus, gotStatus)
|
|
|
}
|
|
|
@@ -516,19 +586,35 @@ func TestServeTailscaleAuth(t *testing.T) {
|
|
|
t.Fatal(err)
|
|
|
}
|
|
|
}
|
|
|
- if !reflect.DeepEqual(gotResp, tt.wantResp) {
|
|
|
- t.Errorf("wrong response; want=%v, got=%v", tt.wantResp, gotResp)
|
|
|
+ if diff := cmp.Diff(gotResp, tt.wantResp); diff != "" {
|
|
|
+ t.Errorf("wrong response; (-got+want):%v", diff)
|
|
|
}
|
|
|
+ // Validate cookie creation.
|
|
|
+ sessionID := tt.cookie
|
|
|
var gotCookie bool
|
|
|
for _, c := range w.Result().Cookies() {
|
|
|
if c.Name == sessionCookieName {
|
|
|
gotCookie = true
|
|
|
+ sessionID = c.Value
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
if gotCookie != tt.wantNewCookie {
|
|
|
t.Errorf("wantNewCookie wrong; want=%v, got=%v", tt.wantNewCookie, gotCookie)
|
|
|
}
|
|
|
+ // Validate browser session contents.
|
|
|
+ var gotSesson *browserSession
|
|
|
+ if s, ok := s.browserSessions.Load(sessionID); ok {
|
|
|
+ gotSesson = s.(*browserSession)
|
|
|
+ }
|
|
|
+ if tt.wantSession != nil && tt.wantSession.ID == "GENERATED_ID" {
|
|
|
+ // If requested, swap in the generated session ID before
|
|
|
+ // comparing got/want.
|
|
|
+ tt.wantSession.ID = sessionID
|
|
|
+ }
|
|
|
+ if diff := cmp.Diff(gotSesson, tt.wantSession); diff != "" {
|
|
|
+ t.Errorf("wrong session; (-got+want):%v", diff)
|
|
|
+ }
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
@@ -572,14 +658,6 @@ func mockLocalAPI(t *testing.T, whoIs map[string]*apitype.WhoIsResponse, self fu
|
|
|
}
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
return
|
|
|
- case "/localapi/v0/prefs":
|
|
|
- prefs := ipn.Prefs{ControlURL: testControlURL}
|
|
|
- if err := json.NewEncoder(w).Encode(prefs); err != nil {
|
|
|
- http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
- return
|
|
|
- }
|
|
|
- w.Header().Set("Content-Type", "application/json")
|
|
|
- return
|
|
|
case "/localapi/v0/debug-web-client": // used by TestServeTailscaleAuth
|
|
|
type reqData struct {
|
|
|
ID string
|
|
|
@@ -596,7 +674,7 @@ func mockLocalAPI(t *testing.T, whoIs map[string]*apitype.WhoIsResponse, self fu
|
|
|
}
|
|
|
var resp *tailcfg.WebClientAuthResponse
|
|
|
if data.ID == "" {
|
|
|
- resp = &tailcfg.WebClientAuthResponse{URL: testControlURL + testAuthPath}
|
|
|
+ resp = &tailcfg.WebClientAuthResponse{ID: testAuthPath, URL: testControlURL + testAuthPath}
|
|
|
} else if data.ID == testAuthPathSuccess {
|
|
|
resp = &tailcfg.WebClientAuthResponse{Complete: true}
|
|
|
} else if data.ID == testAuthPathError {
|