cmd/tailscale: use request Schema+Host for QNAP authLogin.cgi

QNAP allows users to set the port number for the management WebUI,
which includes authLogin.cgi. If they do, then connecting to
localhost:8080 fails.

https://github.com/tailscale/tailscale-qpkg/issues/74#issuecomment-1407486911

Fixes https://github.com/tailscale/tailscale/issues/7108

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
pull/7187/merge
Denton Gentry 2 years ago committed by Denton Gentry
parent 06302e30ae
commit 51288221ce

@ -228,33 +228,48 @@ func qnapAuthn(r *http.Request) (string, *qnapAuthResponse, error) {
return "", nil, fmt.Errorf("not authenticated by any mechanism") return "", nil, fmt.Errorf("not authenticated by any mechanism")
} }
func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResponse, error) { // qnapAuthnURL returns the auth URL to use by inferring where the UI is
query := url.Values{ // running based on the request URL. This is necessary because QNAP has so
"qtoken": []string{token}, // many options, see https://github.com/tailscale/tailscale/issues/7108
"user": []string{user}, // and https://github.com/tailscale/tailscale/issues/6903
func qnapAuthnURL(requestUrl string, query url.Values) string {
in, err := url.Parse(requestUrl)
scheme := ""
host := ""
if err != nil || in.Scheme == "" {
log.Printf("Cannot parse QNAP login URL %v", err)
// try localhost and hope for the best
scheme = "http"
host = "localhost"
} else {
scheme = in.Scheme
host = in.Host
} }
u := url.URL{ u := url.URL{
Scheme: "http", Scheme: scheme,
Host: "127.0.0.1:8080", Host: host,
Path: "/cgi-bin/authLogin.cgi", Path: "/cgi-bin/authLogin.cgi",
RawQuery: query.Encode(), RawQuery: query.Encode(),
} }
return qnapAuthnFinish(user, u.String()) return u.String()
} }
func qnapAuthnSid(r *http.Request, user, sid string) (string, *qnapAuthResponse, error) { func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResponse, error) {
query := url.Values{ query := url.Values{
"sid": []string{sid}, "qtoken": []string{token},
"user": []string{user},
} }
u := url.URL{ return qnapAuthnFinish(user, qnapAuthnURL(r.URL.String(), query))
Scheme: "http",
Host: "127.0.0.1:8080",
Path: "/cgi-bin/authLogin.cgi",
RawQuery: query.Encode(),
} }
return qnapAuthnFinish(user, u.String()) func qnapAuthnSid(r *http.Request, user, sid string) (string, *qnapAuthResponse, error) {
query := url.Values{
"sid": []string{sid},
}
return qnapAuthnFinish(user, qnapAuthnURL(r.URL.String(), query))
} }
func qnapAuthnFinish(user, url string) (string, *qnapAuthResponse, error) { func qnapAuthnFinish(user, url string) (string, *qnapAuthResponse, error) {

@ -3,7 +3,10 @@
package cli package cli
import "testing" import (
"net/url"
"testing"
)
func TestUrlOfListenAddr(t *testing.T) { func TestUrlOfListenAddr(t *testing.T) {
tests := []struct { tests := []struct {
@ -34,9 +37,65 @@ func TestUrlOfListenAddr(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
url := urlOfListenAddr(tt.in) u := urlOfListenAddr(tt.in)
if url != tt.want { if u != tt.want {
t.Errorf("expected url: %q, got: %q", tt.want, url) t.Errorf("expected url: %q, got: %q", tt.want, u)
}
})
}
}
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)
} }
}) })
} }

Loading…
Cancel
Save