Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions backend/helpers/pluginhelper/api/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,8 @@ func NewApiClient(
if err != nil {
return nil, errors.Convert(err)
}
// check connectivity
res, err := apiClient.Get("/", nil, nil)
if err != nil {
return nil, err
}
if res.StatusCode == http.StatusBadGateway {
return nil, errors.BadInput.New(fmt.Sprintf("fail to connect to %v via %v", endpoint, proxy))
}
// No connectivity request here: endpoints behind SSO may redirect the
// API root to a login page and fail. Plugins test their own API instead.
} else {
// check connectivity
parsedUrl, err := url.Parse(endpoint)
Expand Down
33 changes: 33 additions & 0 deletions backend/helpers/pluginhelper/api/api_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@ limitations under the License.
package api

import (
"context"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"

"github.com/apache/devlake/core/config"
"github.com/apache/devlake/core/errors"
implcontext "github.com/apache/devlake/impls/context"
"github.com/apache/devlake/impls/logruslog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestApiClientBlackList(t *testing.T) {
Expand Down Expand Up @@ -95,3 +102,29 @@ func TestApiClientBlackList(t *testing.T) {
})
}
}

// TestNewApiClientWithProxyDoesNotProbeEndpoint is a regression test for
// apache/devlake#8632: creating a client with a proxy must not send a request
// to the endpoint root. Jira behind SSO redirects that root to a login page,
// which made client creation fail with "stopped after 10 redirects".
func TestNewApiClientWithProxyDoesNotProbeEndpoint(t *testing.T) {
var proxied atomic.Int32
// Behaves like a proxy in front of an SSO-protected server: every request
// is redirected back to itself, so any probe would end in a redirect loop.
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proxied.Add(1)
http.Redirect(w, r, r.URL.String(), http.StatusFound)
}))
defer proxy.Close()

br := implcontext.NewDefaultBasicRes(config.GetConfig(), logruslog.Global, nil)
client, err := NewApiClient(context.Background(), "http://jira.example.invalid/rest/", nil, 0, proxy.URL, br)

require.Nil(t, err)
require.NotNil(t, client)
assert.Equal(t, int32(0), proxied.Load(), "NewApiClient must not send requests through the proxy")

// Requests made later still go through the proxy.
_, _ = client.Get("api/2/serverInfo", nil, nil)
assert.Greater(t, proxied.Load(), int32(0), "requests should be routed through the proxy")
}
Loading