2023-08-08 23:58:45 +00:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package web
import (
2023-08-28 20:44:48 +00:00
"fmt"
"io"
"net/http"
"net/http/httptest"
2023-08-08 23:58:45 +00:00
"net/url"
2023-08-28 20:44:48 +00:00
"strings"
2023-08-08 23:58:45 +00:00
"testing"
2023-08-28 20:44:48 +00:00
"tailscale.com/client/tailscale"
"tailscale.com/net/memnet"
2023-08-08 23:58:45 +00:00
)
func TestQnapAuthnURL ( t * testing . T ) {
query := url . Values {
"qtoken" : [ ] string { "token" } ,
}
tests := [ ] struct {
name string
in string
want string
} {
{
name : "localhost http" ,
in : "http://localhost:8088/" ,
want : "http://localhost:8088/cgi-bin/authLogin.cgi?qtoken=token" ,
} ,
{
name : "localhost https" ,
in : "https://localhost:5000/" ,
want : "https://localhost:5000/cgi-bin/authLogin.cgi?qtoken=token" ,
} ,
{
name : "IP http" ,
in : "http://10.1.20.4:80/" ,
want : "http://10.1.20.4:80/cgi-bin/authLogin.cgi?qtoken=token" ,
} ,
{
name : "IP6 https" ,
in : "https://[ff7d:0:1:2::1]/" ,
want : "https://[ff7d:0:1:2::1]/cgi-bin/authLogin.cgi?qtoken=token" ,
} ,
{
name : "hostname https" ,
in : "https://qnap.example.com/" ,
want : "https://qnap.example.com/cgi-bin/authLogin.cgi?qtoken=token" ,
} ,
{
name : "invalid URL" ,
in : "This is not a URL, it is a really really really really really really really really really really really really long string to exercise the URL truncation code in the error path." ,
want : "http://localhost/cgi-bin/authLogin.cgi?qtoken=token" ,
} ,
{
name : "err != nil" ,
in : "http://192.168.0.%31/" ,
want : "http://localhost/cgi-bin/authLogin.cgi?qtoken=token" ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
u := qnapAuthnURL ( tt . in , query )
if u != tt . want {
t . Errorf ( "expected url: %q, got: %q" , tt . want , u )
}
} )
}
}
2023-08-28 20:44:48 +00:00
// TestServeAPI tests the web client api's handling of
// 1. invalid endpoint errors
// 2. localapi proxy allowlist
func TestServeAPI ( t * testing . T ) {
lal := memnet . Listen ( "local-tailscaled.sock:80" )
defer lal . Close ( )
// Serve dummy localapi. Just returns "success".
2023-08-30 01:50:04 +00:00
localapi := & http . Server { Handler : http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintf ( w , "success" )
} ) }
defer localapi . Close ( )
go localapi . Serve ( lal )
2023-08-28 20:44:48 +00:00
s := & Server { lc : & tailscale . LocalClient { Dial : lal . Dial } }
tests := [ ] struct {
name string
reqPath string
wantResp string
wantStatus int
} { {
name : "invalid_endpoint" ,
reqPath : "/not-an-endpoint" ,
wantResp : "invalid endpoint" ,
wantStatus : http . StatusNotFound ,
} , {
name : "not_in_localapi_allowlist" ,
reqPath : "/local/v0/not-allowlisted" ,
wantResp : "/v0/not-allowlisted not allowed from localapi proxy" ,
wantStatus : http . StatusForbidden ,
} , {
name : "in_localapi_allowlist" ,
reqPath : "/local/v0/logout" ,
wantResp : "success" , // Successfully allowed to hit localapi.
wantStatus : http . StatusOK ,
} }
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
r := httptest . NewRequest ( "POST" , "/api" + tt . reqPath , nil )
w := httptest . NewRecorder ( )
s . serveAPI ( w , r )
res := w . Result ( )
defer res . Body . Close ( )
if gotStatus := res . StatusCode ; tt . wantStatus != gotStatus {
t . Errorf ( "wrong status; want=%q, got=%q" , tt . wantStatus , gotStatus )
}
body , err := io . ReadAll ( res . Body )
if err != nil {
t . Fatal ( err )
}
gotResp := strings . TrimSuffix ( string ( body ) , "\n" ) // trim trailing newline
if tt . wantResp != gotResp {
t . Errorf ( "wrong response; want=%q, got=%q" , tt . wantResp , gotResp )
}
} )
}
}