diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index c3971b2a5f..b07fc3732d 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -4,7 +4,15 @@ package git import ( + "bytes" + "encoding/base64" + "io/fs" + "net/http" + "net/http/httptest" + "net/url" + "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -54,3 +62,80 @@ func TestRepoGetDivergingCommits(t *testing.T) { Behind: 2, }, do) } + +func TestCloneCredentials(t *testing.T) { + calledWithoutPassword := false + askpassFile := "" + credentialsFile := "" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/info/refs" { + return + } + + // Get basic authorization. + auth, ok := strings.CutPrefix(req.Header.Get("Authorization"), "Basic ") + if !ok { + w.Header().Set("WWW-Authenticate", `Basic realm="Forgejo"`) + http.Error(w, "require credentials", http.StatusUnauthorized) + return + } + + rawAuth, err := base64.StdEncoding.DecodeString(auth) + require.NoError(t, err) + + user, password, ok := bytes.Cut(rawAuth, []byte{':'}) + assert.True(t, ok) + + // First time around Git tries without password (that's specified in the clone URL). + // It demonstrates it doesn't immediately uses askpass. + if len(password) == 0 { + assert.EqualValues(t, "oauth2", user) + calledWithoutPassword = true + + w.Header().Set("WWW-Authenticate", `Basic realm="Forgejo"`) + http.Error(w, "require credentials", http.StatusUnauthorized) + return + } + + assert.EqualValues(t, "oauth2", user) + assert.EqualValues(t, "some_token", password) + + tmpDir := os.TempDir() + + // Verify that the askpass implementation was used. + files, err := fs.Glob(os.DirFS(tmpDir), "forgejo-askpass*") + require.NoError(t, err) + for _, fileName := range files { + fileContent, err := os.ReadFile(filepath.Join(tmpDir, fileName)) + require.NoError(t, err) + + credentialsPath, ok := bytes.CutPrefix(fileContent, []byte(`exec cat `)) + assert.True(t, ok) + + fileContent, err = os.ReadFile(string(credentialsPath)) + require.NoError(t, err) + assert.EqualValues(t, "some_token", fileContent) + + askpassFile = filepath.Join(tmpDir, fileName) + credentialsFile = string(credentialsPath) + } + })) + + serverURL, err := url.Parse(server.URL) + require.NoError(t, err) + + serverURL.User = url.UserPassword("oauth2", "some_token") + + require.NoError(t, Clone(t.Context(), serverURL.String(), t.TempDir(), CloneRepoOptions{})) + + assert.True(t, calledWithoutPassword) + assert.NotEmpty(t, askpassFile) + assert.NotEmpty(t, credentialsFile) + + // Check that the helper files are gone. + _, err = os.Stat(askpassFile) + require.ErrorIs(t, err, fs.ErrNotExist) + _, err = os.Stat(credentialsFile) + require.ErrorIs(t, err, fs.ErrNotExist) +}