mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-13 19:19:52 +00:00
fix: allow git clone of private repos with anonymous code access (#38074)
Fixes #38062. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
+22
-21
@@ -58,8 +58,6 @@ func CorsHandler() func(next http.Handler) http.Handler {
|
||||
// httpBase does the common work for git http services,
|
||||
// including early response, authentication, repository lookup and permission check.
|
||||
func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
|
||||
reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
|
||||
|
||||
if ctx.FormString("go-get") == "1" {
|
||||
context.EarlyResponseForGoGetMeta(ctx)
|
||||
return nil
|
||||
@@ -93,11 +91,11 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
|
||||
|
||||
isWiki := false
|
||||
unitType := unit.TypeCode
|
||||
|
||||
if strings.HasSuffix(reponame, ".wiki") {
|
||||
repoName := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
|
||||
if strings.HasSuffix(repoName, ".wiki") {
|
||||
isWiki = true
|
||||
unitType = unit.TypeWiki
|
||||
reponame = reponame[:len(reponame)-5]
|
||||
repoName = repoName[:len(repoName)-5]
|
||||
}
|
||||
|
||||
owner := ctx.ContextUser
|
||||
@@ -107,14 +105,14 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
|
||||
}
|
||||
|
||||
repoExist := true
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, reponame)
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName)
|
||||
if err != nil {
|
||||
if !repo_model.IsErrRepoNotExist(err) {
|
||||
ctx.ServerError("GetRepositoryByName", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, reponame); err == nil {
|
||||
if redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, repoName); err == nil {
|
||||
context.RedirectToRepo(ctx.Base, redirectRepoID)
|
||||
return nil
|
||||
}
|
||||
@@ -127,23 +125,26 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only public pull don't need auth.
|
||||
isPublicPull := repoExist && !repo.IsPrivate && isPull
|
||||
askAuth := !isPublicPull || setting.Service.RequireSignInViewStrict
|
||||
|
||||
// don't allow anonymous pulls if organization is not public
|
||||
if isPublicPull {
|
||||
if err := repo.LoadOwner(ctx); err != nil {
|
||||
ctx.ServerError("LoadOwner", err)
|
||||
return nil
|
||||
// Only public pulls don't need auth: repo must exist, not require-sign-in
|
||||
canAnonymousPull := false
|
||||
if isPull && repoExist && !setting.Service.RequireSignInViewStrict {
|
||||
// allow anonymous pulls if owner is public and repo is public (not private)
|
||||
if owner.Visibility == structs.VisibleTypePublic && !repo.IsPrivate {
|
||||
canAnonymousPull = true
|
||||
}
|
||||
// then check "public anonymous access" permission
|
||||
if !canAnonymousPull && ctx.Doer == nil {
|
||||
anonPerm, err := access_model.GetDoerRepoPermission(ctx, repo, nil)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDoerRepoPermission", err)
|
||||
return nil
|
||||
}
|
||||
canAnonymousPull = anonPerm.CanAccess(accessMode, unitType)
|
||||
}
|
||||
|
||||
askAuth = askAuth || (repo.Owner.Visibility != structs.VisibleTypePublic)
|
||||
}
|
||||
|
||||
// check access
|
||||
if askAuth {
|
||||
// rely on the results of Contexter
|
||||
if !canAnonymousPull { // not public pull, then either the pull needs auth, or the push needs "write" permission, so ask auth
|
||||
if !ctx.IsSigned {
|
||||
// TODO: support digit auth - which would be Authorization header with digit
|
||||
if setting.OAuth2.Enabled {
|
||||
@@ -229,7 +230,7 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
|
||||
return nil
|
||||
}
|
||||
|
||||
repo, err = repo_service.PushCreateRepo(ctx, ctx.Doer, owner, reponame)
|
||||
repo, err = repo_service.PushCreateRepo(ctx, ctx.Doer, owner, repoName)
|
||||
if err != nil {
|
||||
log.Error("pushCreateRepo: %v", err)
|
||||
ctx.Status(http.StatusNotFound)
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"testing"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/perm"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
@@ -26,6 +28,8 @@ func TestGitSmartHTTP(t *testing.T) {
|
||||
testGitSmartHTTPTokenScopes(t)
|
||||
testRenamedRepoRedirect(t)
|
||||
testGitArchiveRemote(t, u)
|
||||
t.Run("AnonymousAccess-Repo", func(t *testing.T) { testGitSmartHTTPPrivateRepoAnonymousAccess(t, false) })
|
||||
t.Run("AnonymousAccess-Wiki", func(t *testing.T) { testGitSmartHTTPPrivateRepoAnonymousAccess(t, true) })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -144,3 +148,33 @@ func testGitArchiveRemote(t *testing.T, u *url.URL) {
|
||||
t.Run("Fetch HEAD archive subpath", doGitRemoteArchive(u.String(), "HEAD", "test"))
|
||||
t.Run("list compression options", doGitRemoteArchive(u.String(), "--list"))
|
||||
}
|
||||
|
||||
// testGitSmartHTTPPrivateRepoAnonymousAccess tests that a private repo with
|
||||
// anonymous code access enabled can be cloned without credentials.
|
||||
func testGitSmartHTTPPrivateRepoAnonymousAccess(t *testing.T, isWiki bool) {
|
||||
// repo1 (ID=1) belongs to user2 and is public by default in fixtures
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerName: "user2", Name: "repo1"})
|
||||
unitType := util.Iif(isWiki, unit.TypeWiki, unit.TypeCode)
|
||||
repoLink := "/" + repo.FullName() + util.Iif(isWiki, ".wiki", "")
|
||||
gitPullPath := repoLink + "/info/refs?service=git-upload-pack"
|
||||
gitPushPath := repoLink + "/info/refs?service=git-receive-pack"
|
||||
|
||||
// make the repo private
|
||||
require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), &repo_model.Repository{ID: repo.ID, IsPrivate: true}, "is_private"))
|
||||
|
||||
// without anonymous access: anonymous pull must require auth
|
||||
MakeRequest(t, NewRequest(t, "GET", gitPullPath), http.StatusUnauthorized)
|
||||
|
||||
// enable anonymous read access on the unit
|
||||
require.NoError(t, repo_model.UpdateRepoUnitPublicAccess(t.Context(), &repo_model.RepoUnit{RepoID: repo.ID, Type: unitType, AnonymousAccessMode: perm.AccessModeRead}))
|
||||
|
||||
// with anonymous code access: anonymous pull must succeed without credentials
|
||||
MakeRequest(t, NewRequest(t, "GET", gitPullPath), http.StatusOK)
|
||||
|
||||
// push (receive-pack) must still require auth even with anonymous code access
|
||||
MakeRequest(t, NewRequest(t, "GET", gitPushPath), http.StatusUnauthorized)
|
||||
|
||||
// RequireSignInViewStrict must override anonymous access
|
||||
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||
MakeRequest(t, NewRequest(t, "GET", gitPullPath), http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ RUN_MODE = prod
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = gitea-test.db
|
||||
SQLITE_JOURNAL_MODE = WAL
|
||||
|
||||
[indexer]
|
||||
REPO_INDEXER_ENABLED = true
|
||||
|
||||
Reference in New Issue
Block a user