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/7427/head
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// qnapAuthnURL returns the auth URL to use by inferring where the UI is
|
||||||
|
// running based on the request URL. This is necessary because QNAP has so
|
||||||
|
// many options, see https://github.com/tailscale/tailscale/issues/7108
|
||||||
|
// 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{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: host,
|
||||||
|
Path: "/cgi-bin/authLogin.cgi",
|
||||||
|
RawQuery: query.Encode(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResponse, error) {
|
func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResponse, error) {
|
||||||
query := url.Values{
|
query := url.Values{
|
||||||
"qtoken": []string{token},
|
"qtoken": []string{token},
|
||||||
"user": []string{user},
|
"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) {
|
func qnapAuthnSid(r *http.Request, user, sid string) (string, *qnapAuthResponse, error) {
|
||||||
query := url.Values{
|
query := url.Values{
|
||||||
"sid": []string{sid},
|
"sid": []string{sid},
|
||||||
}
|
}
|
||||||
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 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…
Reference in New Issue