mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
internal/util imported internal/proxyplugin (SharedTransport, FallbackTransport, NewHTTPClient, and WarnIfProxied via proxyPluginStatus), so a foundational util package depended up into a feature package, pulling binding/core/vfs into the transitive cone of every util importer. Move internal/proxyplugin -> internal/transport and make it the single owner of outbound transport: fold the two SharedTransport functions into one Shared() (proxy-plugin override -> LARK_CLI_NO_PROXY -> http.DefaultTransport), and move Fallback/NewHTTPClient/WarnIfProxied/DetectProxyEnv/noProxyTransport out of the now-deleted internal/util/proxy.go into the new package. The proxy-plugin probe is demoted to a private pluginTransport(); the duplicate redactProxyURL collapses to one. internal/util keeps no proxy code and is a leaf again. Re-point all consumers (registry, doctor, config, auth, cmdutil, update) to internal/transport. Behavior-preserving: package move + symbol rename + dedup. Two new tests lock the fail-closed contract (plugin overrides NO_PROXY; malformed config never falls through to direct egress).
196 lines
5.9 KiB
Go
196 lines
5.9 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package transport
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func resetProxyPluginState() {
|
|
loadOnce = sync.Once{}
|
|
loadCfg = nil
|
|
loadErr = nil
|
|
proxyPluginTransport = sync.OnceValue(buildProxyPluginTransport)
|
|
cachedBlockedTransport = sync.OnceValue(buildBlockedTransport)
|
|
}
|
|
|
|
func TestPluginTransport_NotConfigured(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
unsetProxyPluginEnv(t)
|
|
resetProxyPluginState()
|
|
|
|
tr, ok := pluginTransport()
|
|
if ok {
|
|
t.Fatalf("pluginTransport() ok = true, want false")
|
|
}
|
|
if tr != nil {
|
|
t.Fatalf("pluginTransport() transport = %T, want nil", tr)
|
|
}
|
|
}
|
|
|
|
func TestPluginTransport_EnabledReturnsFixedProxy(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
unsetProxyPluginEnv(t)
|
|
resetProxyPluginState()
|
|
|
|
cfgPath := Path()
|
|
writeFile(t, cfgPath, []byte(`{
|
|
"LARKSUITE_CLI_PROXY_ENABLE": true,
|
|
"LARKSUITE_CLI_PROXY_ADDRESS": "http://127.0.0.1:3128",
|
|
"LARKSUITE_CLI_CA_PATH": ""
|
|
}`), 0600)
|
|
|
|
rt, ok := pluginTransport()
|
|
if !ok {
|
|
t.Fatal("pluginTransport() ok = false, want true")
|
|
}
|
|
tr, ok := rt.(*http.Transport)
|
|
if !ok {
|
|
t.Fatalf("pluginTransport() = %T, want *http.Transport", rt)
|
|
}
|
|
u, err := tr.Proxy(&http.Request{URL: &url.URL{Scheme: "https", Host: "open.feishu.cn"}})
|
|
if err != nil {
|
|
t.Fatalf("Proxy() error = %v", err)
|
|
}
|
|
if u == nil || u.String() != "http://127.0.0.1:3128" {
|
|
t.Fatalf("Proxy() = %v, want http://127.0.0.1:3128", u)
|
|
}
|
|
}
|
|
|
|
func TestPluginTransport_InvalidConfigWithNonTransportDefaultFailsClosed(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
unsetProxyPluginEnv(t)
|
|
resetProxyPluginState()
|
|
restoreDefaultTransport := replaceDefaultTransport(okRoundTripper{})
|
|
defer restoreDefaultTransport()
|
|
|
|
writeFile(t, Path(), []byte(`{`), 0600)
|
|
|
|
rt, ok := pluginTransport()
|
|
if !ok {
|
|
t.Fatal("pluginTransport() ok = false, want true")
|
|
}
|
|
if rt == http.DefaultTransport {
|
|
t.Fatalf("pluginTransport() returned http.DefaultTransport, want fail-closed transport")
|
|
}
|
|
resp, err := rt.RoundTrip(&http.Request{URL: &url.URL{Scheme: "https", Host: "open.feishu.cn"}})
|
|
if err == nil {
|
|
t.Fatalf("RoundTrip() error = nil, response = %#v; want fail-closed error", resp)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("RoundTrip() response = %#v, want nil", resp)
|
|
}
|
|
}
|
|
|
|
func TestPluginTransport_InvalidConfigReturnsCachedInstance(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
unsetProxyPluginEnv(t)
|
|
resetProxyPluginState()
|
|
|
|
writeFile(t, Path(), []byte(`{`), 0600)
|
|
|
|
a, ok := pluginTransport()
|
|
if !ok {
|
|
t.Fatal("pluginTransport() ok = false, want true")
|
|
}
|
|
b, ok := pluginTransport()
|
|
if !ok {
|
|
t.Fatal("pluginTransport() ok = false, want true")
|
|
}
|
|
if a != b {
|
|
t.Fatalf("pluginTransport() returned different instances on repeated calls; blocked transport must be cached")
|
|
}
|
|
}
|
|
|
|
func TestBuildProxyPluginTransport_InvalidConfigFailsClosed(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
unsetProxyPluginEnv(t)
|
|
resetProxyPluginState()
|
|
|
|
writeFile(t, Path(), []byte(`{`), 0600)
|
|
|
|
rt := buildProxyPluginTransport()
|
|
if rt == http.DefaultTransport {
|
|
t.Fatalf("buildProxyPluginTransport() returned http.DefaultTransport, want fail-closed transport")
|
|
}
|
|
resp, err := rt.RoundTrip(&http.Request{URL: &url.URL{Scheme: "https", Host: "open.feishu.cn"}})
|
|
if err == nil {
|
|
t.Fatalf("RoundTrip() error = nil, response = %#v; want fail-closed error", resp)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("RoundTrip() response = %#v, want nil", resp)
|
|
}
|
|
}
|
|
|
|
func TestBuildProxyPluginTransport_NonTransportDefaultFailsClosed(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
unsetProxyPluginEnv(t)
|
|
resetProxyPluginState()
|
|
restoreDefaultTransport := replaceDefaultTransport(okRoundTripper{})
|
|
defer restoreDefaultTransport()
|
|
|
|
rt := buildProxyPluginTransport()
|
|
if rt == http.DefaultTransport {
|
|
t.Fatalf("buildProxyPluginTransport() returned http.DefaultTransport, want fail-closed transport")
|
|
}
|
|
resp, err := rt.RoundTrip(&http.Request{URL: &url.URL{Scheme: "https", Host: "open.feishu.cn"}})
|
|
if err == nil {
|
|
t.Fatalf("RoundTrip() error = nil, response = %#v; want fail-closed error", resp)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("RoundTrip() response = %#v, want nil", resp)
|
|
}
|
|
}
|
|
|
|
// TestPluginTransport_InvalidConfigBlockerIsConcreteTransport guards the
|
|
// fail-closed invariant that Fallback relies on: even when
|
|
// http.DefaultTransport is not an *http.Transport, an invalid proxy config must
|
|
// produce a blocked transport that is itself a concrete *http.Transport. If it
|
|
// were a bare RoundTripper, Fallback would downcast-fail and
|
|
// silently degrade it into a direct-egress transport.
|
|
func TestPluginTransport_InvalidConfigBlockerIsConcreteTransport(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
unsetProxyPluginEnv(t)
|
|
resetProxyPluginState()
|
|
restoreDefaultTransport := replaceDefaultTransport(okRoundTripper{})
|
|
defer restoreDefaultTransport()
|
|
|
|
writeFile(t, Path(), []byte(`{`), 0600)
|
|
|
|
rt, ok := pluginTransport()
|
|
if !ok {
|
|
t.Fatal("pluginTransport() ok = false, want true")
|
|
}
|
|
if _, isTransport := rt.(*http.Transport); !isTransport {
|
|
t.Fatalf("pluginTransport() blocked transport = %T, want *http.Transport so Fallback cannot degrade it to direct egress", rt)
|
|
}
|
|
// Must remain fail-closed.
|
|
resp, err := rt.RoundTrip(&http.Request{URL: &url.URL{Scheme: "https", Host: "open.feishu.cn"}})
|
|
if err == nil {
|
|
t.Fatalf("RoundTrip() error = nil, response = %#v; want fail-closed error", resp)
|
|
}
|
|
if resp != nil {
|
|
t.Fatalf("RoundTrip() response = %#v, want nil", resp)
|
|
}
|
|
}
|
|
|
|
type okRoundTripper struct{}
|
|
|
|
func (okRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
|
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))}, nil
|
|
}
|
|
|
|
func replaceDefaultTransport(rt http.RoundTripper) func() {
|
|
original := http.DefaultTransport
|
|
http.DefaultTransport = rt
|
|
return func() {
|
|
http.DefaultTransport = original
|
|
}
|
|
}
|