0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-11 04:55:34 +02:00

lfs: relax lfs checks allowing AGit+LFS

This commit is contained in:
Adam Majer 2026-02-07 20:25:43 +01:00
parent 49e6d5f6d6
commit 380d95b0d2
3 changed files with 124 additions and 3 deletions

View File

@ -333,8 +333,9 @@ func ServCommand(ctx *context.PrivateContext) {
// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
// AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
// The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
// Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == git.CmdVerbReceivePack {
// Here it should relax the permission check for "git push (git-receive-pack)" and LFS upload operations.
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode &&
(verb == git.CmdVerbReceivePack || verb == git.CmdVerbLfsAuthenticate || verb == git.CmdVerbLfsTransfer) {
mode = perm.AccessModeRead
}

View File

@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/httpauth"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
lfs_module "code.gitea.io/gitea/modules/lfs"
@ -537,7 +538,7 @@ func writeStatusMessage(ctx *context.Context, status int, message string) {
// to proceed. This server assumes an HTTP Basic auth format.
func authenticate(ctx *context.Context, repository *repo_model.Repository, authorization string, requireSigned, requireWrite bool) bool {
accessMode := perm_model.AccessModeRead
if requireWrite {
if requireWrite && !git.DefaultFeatures().SupportProcReceive {
accessMode = perm_model.AccessModeWrite
}

View File

@ -0,0 +1,119 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"net/url"
"os"
"path/filepath"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func testAPISaveUserPublicKey(t *testing.T, session *TestSession, username, keyname, content string) {
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
req := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", &api.CreateKeyOption{
Title: keyname,
Key: content,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
}
func TestAgitLFS(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
// Enable LFS
defer tests.PrepareTestEnv(t)()
setting.LFS.StartServer = true
setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs")
t.Run("HTTP", func(t *testing.T) {
dstPath := t.TempDir()
// user4 has read access to repo1 (owned by user2)
u.Path = "user2/repo1.git"
u.User = url.UserPassword("user4", userPassword)
doGitClone(dstPath, u)(t)
// Setup LFS in the repo
_, _, err := gitcmd.NewCommand("lfs", "install").WithDir(dstPath).RunStdString(t.Context())
assert.NoError(t, err)
_, _, err = gitcmd.NewCommand("lfs", "track", "*.bin").WithDir(dstPath).RunStdString(t.Context())
assert.NoError(t, err)
assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "large.bin"), []byte("this is a large file"), 0o644))
assert.NoError(t, git.AddChanges(t.Context(), dstPath, true))
signature := git.Signature{
Email: "user4@example.com",
Name: "user4",
}
assert.NoError(t, git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
Committer: &signature,
Author: &signature,
Message: "Add LFS file",
}))
// push to create an agit pull request
assert.NoError(t, gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-lfs-http").
WithDir(dstPath).
Run(t.Context()))
})
t.Run("SSH", func(t *testing.T) {
dstPath := t.TempDir()
// user4 has read access to repo1 (owned by user2)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
sshURL := createSSHUrl(repo.FullName()+".git", u)
withKeyFile(t, "id_rsa", func(keyFile string) {
t.Run("AddKey", func(t *testing.T) {
session := loginUser(t, "user4")
content, _ := os.ReadFile(keyFile + ".pub")
testAPISaveUserPublicKey(t, session, "user4", "user4-agit-lfs", string(content))
})
doGitClone(dstPath, sshURL)(t)
// Setup LFS in the repo
_, _, err := gitcmd.NewCommand("lfs", "install").WithDir(dstPath).RunStdString(t.Context())
assert.NoError(t, err)
_, _, err = gitcmd.NewCommand("lfs", "track", "*.bin").WithDir(dstPath).RunStdString(t.Context())
assert.NoError(t, err)
assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "large-ssh.bin"), []byte("this is a large file via ssh"), 0o644))
assert.NoError(t, git.AddChanges(t.Context(), dstPath, true))
signature := git.Signature{
Email: "user4@example.com",
Name: "user4",
}
assert.NoError(t, git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
Committer: &signature,
Author: &signature,
Message: "Add LFS file via SSH",
}))
// push to create an agit pull request
assert.NoError(t, gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-lfs-ssh").
WithDir(dstPath).
Run(t.Context()))
})
})
})
}