mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-08 17:40:28 +00:00
feat: move native communication to FFI (#1457)
This commit is contained in:
86 files changed
+4558
-886
No files matched your search
+95
-96
@@ -643,7 +643,7 @@ func TestWebFsEnhance(t *testing.T) {
|
||||
|
||||
func TestDoProxy(t *testing.T) {
|
||||
doTest(func() {
|
||||
code, respBody := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{
|
||||
code, respBody := doHttpRequest0(http.MethodGet, "/api/web/proxy", map[string]string{
|
||||
"X-Target-Uri": "https://github.com/GopeedLab/gopeed/raw/695da7ea87d2b455552b709d3cb4d7879484d4d1/README.md",
|
||||
}, nil)
|
||||
if code != http.StatusOK {
|
||||
@@ -657,7 +657,7 @@ func TestDoProxy(t *testing.T) {
|
||||
})
|
||||
|
||||
doTest(func() {
|
||||
code, _ := doHttpRequest0(http.MethodGet, "/api/v1/proxy", map[string]string{
|
||||
code, _ := doHttpRequest0(http.MethodGet, "/api/web/proxy", map[string]string{
|
||||
"X-Target-Uri": "https://github.com/GopeedLab/gopeed/raw/695da7ea87d2b455552b709d3cb4d7879484d4d1/NOT_FOUND",
|
||||
}, nil)
|
||||
if code != http.StatusNotFound {
|
||||
@@ -802,6 +802,28 @@ func TestApiToken(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestBuildServerRejectsWebAPITokenWithoutWebAuth(t *testing.T) {
|
||||
cfg := (&model.StartConfig{
|
||||
Storage: model.StorageMem,
|
||||
WebEnable: true,
|
||||
ApiToken: "123456",
|
||||
}).Init()
|
||||
|
||||
server, listener, err := BuildServer(cfg)
|
||||
if err == nil {
|
||||
if listener != nil {
|
||||
listener.Close()
|
||||
}
|
||||
if server != nil {
|
||||
server.Close()
|
||||
}
|
||||
t.Fatal("BuildServer() should reject an API token without web authentication")
|
||||
}
|
||||
if server != nil || listener != nil {
|
||||
t.Fatal("invalid web authentication config must fail before opening a listener")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorization(t *testing.T) {
|
||||
var cfg = &model.StartConfig{}
|
||||
cfg.Init()
|
||||
@@ -827,118 +849,74 @@ func TestAuthorization(t *testing.T) {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, loginHeaders, loginBody := doHttpRequest1(http.MethodPost, "/api/web/login", nil, cfg.WebAuth)
|
||||
if status != http.StatusOK {
|
||||
t.Fatalf("login status = %d, want %d", status, http.StatusOK)
|
||||
}
|
||||
var loginResult model.Result[any]
|
||||
if err := json.Unmarshal(loginBody, &loginResult); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loginResult.Data != nil {
|
||||
t.Fatalf("login response exposed session data: %#v", loginResult.Data)
|
||||
}
|
||||
loginResponse := &http.Response{Header: http.Header{"Set-Cookie": []string{loginHeaders["Set-Cookie"]}}}
|
||||
cookies := loginResponse.Cookies()
|
||||
if len(cookies) != 1 {
|
||||
t.Fatalf("login cookies = %d, want 1", len(cookies))
|
||||
}
|
||||
authCookie := cookies[0]
|
||||
if authCookie.Name != webAuthCookieName || authCookie.Value == "" || authCookie.Path != "/" ||
|
||||
!authCookie.HttpOnly || authCookie.SameSite != http.SameSiteLaxMode || authCookie.MaxAge <= 0 {
|
||||
t.Fatalf("unexpected login cookie: %#v", authCookie)
|
||||
}
|
||||
bearerHeaders := map[string]string{
|
||||
"Authorization": "Bearer " + authCookie.Value,
|
||||
}
|
||||
authCookieHeader := map[string]string{
|
||||
"Cookie": fmt.Sprintf("%s=%s", authCookie.Name, authCookie.Value),
|
||||
}
|
||||
|
||||
for _, filePath := range []string{
|
||||
"/fs/extensions/not_exist/icon.png",
|
||||
"/fs/tasks/not_exist/file.bin",
|
||||
} {
|
||||
status, _ = doHttpRequest0(http.MethodGet, filePath, nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("unauthenticated %s status = %d, want %d", filePath, status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, filePath, bearerHeaders, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("Bearer-only %s status = %d, want %d", filePath, status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, filePath, authCookieHeader, nil)
|
||||
if status != http.StatusNotFound {
|
||||
t.Errorf("cookie-authenticated %s status = %d, want %d", filePath, status, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/future-resource", nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("unauthenticated future /fs route status = %d, want %d", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/extensions/not_exist/icon.png", map[string]string{
|
||||
"Cookie": webAuthCookieName + "=invalid",
|
||||
}, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("invalid-cookie file status = %d, want %d", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", bearerHeaders, nil)
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Authorization": "Bearer invalid-session",
|
||||
}, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("Bearer-authenticated API status = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Authorization": "xxx",
|
||||
"Cookie": webAuthCookieName + "=invalid-session",
|
||||
}, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, loginHeaders, loginBody := doHttpRequest1(http.MethodPost, "/api/web/login", nil, cfg.WebAuth)
|
||||
if status != http.StatusOK {
|
||||
t.Fatalf("cookie login got = %v, want %v", status, http.StatusOK)
|
||||
}
|
||||
setCookie := loginHeaders["Set-Cookie"]
|
||||
if !strings.Contains(setCookie, webAuthCookieName+"=") ||
|
||||
!strings.Contains(setCookie, "HttpOnly") ||
|
||||
!strings.Contains(setCookie, "SameSite=Lax") {
|
||||
t.Fatalf("unexpected auth cookie: %q", setCookie)
|
||||
}
|
||||
cookieHeader := strings.SplitN(setCookie, ";", 2)[0]
|
||||
sessionID := strings.TrimPrefix(cookieHeader, webAuthCookieName+"=")
|
||||
if sessionID == "" || strings.Contains(string(loginBody), sessionID) {
|
||||
t.Fatalf("session id must only be returned in the HttpOnly cookie")
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Authorization": "xxx",
|
||||
"Cookie": cookieHeader,
|
||||
}, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("cookie authorization got = %v, want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
buildToken := func(username, password string, ts int64) string {
|
||||
token, _ := aesEncrypt(aesKey, []byte(fmt.Sprintf("%s:%s:%d", username, password, ts)))
|
||||
return token
|
||||
}
|
||||
|
||||
fakeToken := buildToken("fake", "fake", time.Now().Unix())
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Cookie": fmt.Sprintf("%s=%s", webAuthCookieName, fakeToken),
|
||||
}, nil)
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/extensions/not_exist/icon.png", nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
expireToken := buildToken(cfg.WebAuth.Username, cfg.WebAuth.Password, time.Now().Add(-time.Hour*8*24).Unix())
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Cookie": fmt.Sprintf("%s=%s", webAuthCookieName, expireToken),
|
||||
}, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusUnauthorized)
|
||||
t.Errorf("unauthenticated fs request got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/extensions/not_exist/icon.png", map[string]string{
|
||||
"Cookie": fmt.Sprintf("%s=%s", webAuthCookieName, expireToken),
|
||||
"Cookie": cookieHeader,
|
||||
}, nil)
|
||||
if status != http.StatusNotFound {
|
||||
t.Errorf("authenticated fs request got = %v, want %v", status, http.StatusNotFound)
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/tasks/not_exist/file.png", nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("unauthenticated task fs request got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/tasks/not_exist/file.png", map[string]string{
|
||||
"Cookie": cookieHeader,
|
||||
}, nil)
|
||||
if status != http.StatusNotFound {
|
||||
t.Errorf("authenticated task fs request got = %v, want %v", status, http.StatusNotFound)
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/future-resource", nil, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("unauthenticated future /fs route got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/fs/future-resource", map[string]string{
|
||||
"Authorization": "Bearer " + sessionID,
|
||||
}, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("expired-cookie file status = %d, want %d", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", authCookieHeader, nil)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||
t.Errorf("Bearer-only future /fs route got = %v, want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
@@ -947,17 +925,16 @@ func TestAuthorization(t *testing.T) {
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Cookie": fmt.Sprintf("%s=%s", authCookie.Name, authCookie.Value),
|
||||
"Cookie": cookieHeader,
|
||||
"X-Api-Token": cfg.ApiToken,
|
||||
}, nil)
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("TestAuthorization() got = %v, want %v", status, http.StatusOK)
|
||||
t.Errorf("cookie and API token authorization got = %v, want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
status, _ = doHttpRequest0(http.MethodGet, "/api/v1/config", map[string]string{
|
||||
"Cookie": fmt.Sprintf("%s=%s", authCookie.Name, authCookie.Value),
|
||||
"Cookie": cookieHeader,
|
||||
"X-Api-Token": "",
|
||||
}, nil)
|
||||
if status != http.StatusUnauthorized {
|
||||
@@ -965,6 +942,28 @@ func TestAuthorization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebSessionStoreExpiry(t *testing.T) {
|
||||
now := time.Date(2026, time.August, 30, 12, 0, 0, 0, time.UTC)
|
||||
store := newWebSessionStore()
|
||||
store.now = func() time.Time { return now }
|
||||
|
||||
sessionID, expiresAt, err := store.create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sessionID == "" || expiresAt != now.Add(webAuthSessionTTL) {
|
||||
t.Fatalf("unexpected session: id=%q expiresAt=%v", sessionID, expiresAt)
|
||||
}
|
||||
if !store.valid(sessionID) {
|
||||
t.Fatal("new session should be valid")
|
||||
}
|
||||
|
||||
now = expiresAt
|
||||
if store.valid(sessionID) {
|
||||
t.Fatal("expired session should be invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildServerPropagatesWebViewProvider(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user