Fix the bug when ssh clone with redirect user or repository (#36039) (#36090)

Backport #36039 by @lunny

Fix #36026 

The redirect should be checked when original user/repo doesn't exist.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
Giteabot 2025-12-05 12:06:14 +08:00 committed by GitHub
parent 544450a212
commit b1b35e934e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 88 additions and 40 deletions

View File

@ -108,21 +108,19 @@ func ServCommand(ctx *context.PrivateContext) {
results.RepoName = repoName[:len(repoName)-5] results.RepoName = repoName[:len(repoName)-5]
} }
// Check if there is a user redirect for the requested owner
redirectedUserID, err := user_model.LookupUserRedirect(ctx, results.OwnerName)
if err == nil {
owner, err := user_model.GetUserByID(ctx, redirectedUserID)
if err == nil {
log.Info("User %s has been redirected to %s", results.OwnerName, owner.Name)
results.OwnerName = owner.Name
} else {
log.Warn("User %s has a redirect to user with ID %d, but no user with this ID could be found. Trying without redirect...", results.OwnerName, redirectedUserID)
}
}
owner, err := user_model.GetUserByName(ctx, results.OwnerName) owner, err := user_model.GetUserByName(ctx, results.OwnerName)
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if !user_model.IsErrUserNotExist(err) {
log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err),
})
return
}
// Check if there is a user redirect for the requested owner
redirectedUserID, err := user_model.LookupUserRedirect(ctx, results.OwnerName)
if err != nil {
// User is fetching/cloning a non-existent repository // User is fetching/cloning a non-existent repository
log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr()) log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr())
ctx.JSON(http.StatusNotFound, private.Response{ ctx.JSON(http.StatusNotFound, private.Response{
@ -130,11 +128,20 @@ func ServCommand(ctx *context.PrivateContext) {
}) })
return return
} }
log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
ctx.JSON(http.StatusForbidden, private.Response{ redirectUser, err := user_model.GetUserByID(ctx, redirectedUserID)
UserMsg: fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err), if err != nil {
}) // User is fetching/cloning a non-existent repository
return log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr())
ctx.JSON(http.StatusNotFound, private.Response{
UserMsg: fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName),
})
return
}
log.Info("User %s has been redirected to %s", results.OwnerName, redirectUser.Name)
results.OwnerName = redirectUser.Name
owner = redirectUser
} }
if !owner.IsOrganization() && !owner.IsActive { if !owner.IsOrganization() && !owner.IsActive {
ctx.JSON(http.StatusForbidden, private.Response{ ctx.JSON(http.StatusForbidden, private.Response{
@ -143,24 +150,33 @@ func ServCommand(ctx *context.PrivateContext) {
return return
} }
redirectedRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, results.RepoName)
if err == nil {
redirectedRepo, err := repo_model.GetRepositoryByID(ctx, redirectedRepoID)
if err == nil {
log.Info("Repository %s/%s has been redirected to %s/%s", results.OwnerName, results.RepoName, redirectedRepo.OwnerName, redirectedRepo.Name)
results.RepoName = redirectedRepo.Name
results.OwnerName = redirectedRepo.OwnerName
owner.ID = redirectedRepo.OwnerID
} else {
log.Warn("Repo %s/%s has a redirect to repo with ID %d, but no repo with this ID could be found. Trying without redirect...", results.OwnerName, results.RepoName, redirectedRepoID)
}
}
// Now get the Repository and set the results section // Now get the Repository and set the results section
repoExist := true repoExist := true
repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, results.RepoName) repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, results.RepoName)
if err != nil { if err != nil {
if repo_model.IsErrRepoNotExist(err) { if !repo_model.IsErrRepoNotExist(err) {
log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err),
})
return
}
redirectedRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, results.RepoName)
if err == nil {
redirectedRepo, err := repo_model.GetRepositoryByID(ctx, redirectedRepoID)
if err == nil {
log.Info("Repository %s/%s has been redirected to %s/%s", results.OwnerName, results.RepoName, redirectedRepo.OwnerName, redirectedRepo.Name)
results.RepoName = redirectedRepo.Name
results.OwnerName = redirectedRepo.OwnerName
repo = redirectedRepo
owner.ID = redirectedRepo.OwnerID
} else {
log.Warn("Repo %s/%s has a redirect to repo with ID %d, but no repo with this ID could be found. Trying without redirect...", results.OwnerName, results.RepoName, redirectedRepoID)
}
}
if repo == nil {
repoExist = false repoExist = false
if mode == perm.AccessModeRead { if mode == perm.AccessModeRead {
// User is fetching/cloning a non-existent repository // User is fetching/cloning a non-existent repository
@ -170,13 +186,6 @@ func ServCommand(ctx *context.PrivateContext) {
}) })
return return
} }
// else fallthrough (push-to-create may kick in below)
} else {
log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err),
})
return
} }
} }

View File

@ -6,9 +6,13 @@ package integration
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"os"
"testing" "testing"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
) )
func TestGitSSHRedirect(t *testing.T) { func TestGitSSHRedirect(t *testing.T) {
@ -16,7 +20,8 @@ func TestGitSSHRedirect(t *testing.T) {
} }
func testGitSSHRedirect(t *testing.T, u *url.URL) { func testGitSSHRedirect(t *testing.T, u *url.URL) {
apiTestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) apiTestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization)
session := loginUser(t, "user2")
withKeyFile(t, "my-testing-key", func(keyFile string) { withKeyFile(t, "my-testing-key", func(keyFile string) {
t.Run("CreateUserKey", doAPICreateUserKey(apiTestContext, "test-key", keyFile)) t.Run("CreateUserKey", doAPICreateUserKey(apiTestContext, "test-key", keyFile))
@ -38,5 +43,39 @@ func testGitSSHRedirect(t *testing.T, u *url.URL) {
t.Run("Clone", doGitClone(t.TempDir(), cloneURL)) t.Run("Clone", doGitClone(t.TempDir(), cloneURL))
}) })
} }
doAPICreateOrganization(apiTestContext, &structs.CreateOrgOption{
UserName: "olduser2",
FullName: "Old User2",
})(t)
cloneURL := createSSHUrl("olduser2/repo1.git", u)
t.Run("Clone Should Fail", doGitCloneFail(cloneURL))
doAPICreateOrganizationRepository(apiTestContext, "olduser2", &structs.CreateRepoOption{
Name: "repo1",
AutoInit: true,
})(t)
testEditFile(t, session, "olduser2", "repo1", "master", "README.md", "This is olduser2's repo1\n")
dstDir := t.TempDir()
t.Run("Clone", doGitClone(dstDir, cloneURL))
readMEContent, err := os.ReadFile(dstDir + "/README.md")
assert.NoError(t, err)
assert.Equal(t, "This is olduser2's repo1\n", string(readMEContent))
apiTestContext2 := NewAPITestContext(t, "user2", "oldrepo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization)
doAPICreateRepository(apiTestContext2, false)(t)
testEditFile(t, session, "user2", "oldrepo1", "master", "README.md", "This is user2's oldrepo1\n")
dstDir = t.TempDir()
cloneURL = createSSHUrl("user2/oldrepo1.git", u)
t.Run("Clone", doGitClone(dstDir, cloneURL))
readMEContent, err = os.ReadFile(dstDir + "/README.md")
assert.NoError(t, err)
assert.Equal(t, "This is user2's oldrepo1\n", string(readMEContent))
cloneURL = createSSHUrl("olduser2/oldrepo1.git", u)
t.Run("Clone Should Fail", doGitCloneFail(cloneURL))
}) })
} }