mirror of
https://github.com/go-gitea/gitea.git
synced 2025-06-10 11:14:41 +02:00
Backport #34408 by @lunny Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
38cc7453e2
commit
6d738fecc4
79
cmd/serv.go
79
cmd/serv.go
@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -20,7 +19,7 @@ import (
|
|||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/lfstransfer"
|
"code.gitea.io/gitea/modules/lfstransfer"
|
||||||
@ -37,14 +36,6 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
verbUploadPack = "git-upload-pack"
|
|
||||||
verbUploadArchive = "git-upload-archive"
|
|
||||||
verbReceivePack = "git-receive-pack"
|
|
||||||
verbLfsAuthenticate = "git-lfs-authenticate"
|
|
||||||
verbLfsTransfer = "git-lfs-transfer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdServ represents the available serv sub-command.
|
// CmdServ represents the available serv sub-command.
|
||||||
var CmdServ = &cli.Command{
|
var CmdServ = &cli.Command{
|
||||||
Name: "serv",
|
Name: "serv",
|
||||||
@ -78,22 +69,6 @@ func setup(ctx context.Context, debug bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// keep getAccessMode() in sync
|
|
||||||
allowedCommands = container.SetOf(
|
|
||||||
verbUploadPack,
|
|
||||||
verbUploadArchive,
|
|
||||||
verbReceivePack,
|
|
||||||
verbLfsAuthenticate,
|
|
||||||
verbLfsTransfer,
|
|
||||||
)
|
|
||||||
allowedCommandsLfs = container.SetOf(
|
|
||||||
verbLfsAuthenticate,
|
|
||||||
verbLfsTransfer,
|
|
||||||
)
|
|
||||||
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
||||||
// The output will be passed to git client and shown to user.
|
// The output will be passed to git client and shown to user.
|
||||||
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
||||||
@ -139,19 +114,20 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
|
|||||||
|
|
||||||
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
||||||
switch verb {
|
switch verb {
|
||||||
case verbUploadPack, verbUploadArchive:
|
case git.CmdVerbUploadPack, git.CmdVerbUploadArchive:
|
||||||
return perm.AccessModeRead
|
return perm.AccessModeRead
|
||||||
case verbReceivePack:
|
case git.CmdVerbReceivePack:
|
||||||
return perm.AccessModeWrite
|
return perm.AccessModeWrite
|
||||||
case verbLfsAuthenticate, verbLfsTransfer:
|
case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer:
|
||||||
switch lfsVerb {
|
switch lfsVerb {
|
||||||
case "upload":
|
case git.CmdSubVerbLfsUpload:
|
||||||
return perm.AccessModeWrite
|
return perm.AccessModeWrite
|
||||||
case "download":
|
case git.CmdSubVerbLfsDownload:
|
||||||
return perm.AccessModeRead
|
return perm.AccessModeRead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// should be unreachable
|
// should be unreachable
|
||||||
|
setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
|
||||||
return perm.AccessModeNone
|
return perm.AccessModeNone
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,12 +206,12 @@ func runServ(c *cli.Context) error {
|
|||||||
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
||||||
}
|
}
|
||||||
|
|
||||||
words, err := shellquote.Split(cmd)
|
sshCmdArgs, err := shellquote.Split(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
|
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(words) < 2 {
|
if len(sshCmdArgs) < 2 {
|
||||||
if git.DefaultFeatures().SupportProcReceive {
|
if git.DefaultFeatures().SupportProcReceive {
|
||||||
// for AGit Flow
|
// for AGit Flow
|
||||||
if cmd == "ssh_info" {
|
if cmd == "ssh_info" {
|
||||||
@ -246,25 +222,21 @@ func runServ(c *cli.Context) error {
|
|||||||
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
verb := words[0]
|
repoPath := strings.TrimPrefix(sshCmdArgs[1], "/")
|
||||||
repoPath := strings.TrimPrefix(words[1], "/")
|
repoPathFields := strings.SplitN(repoPath, "/", 2)
|
||||||
|
if len(repoPathFields) != 2 {
|
||||||
var lfsVerb string
|
|
||||||
|
|
||||||
rr := strings.SplitN(repoPath, "/", 2)
|
|
||||||
if len(rr) != 2 {
|
|
||||||
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
username := rr[0]
|
username := repoPathFields[0]
|
||||||
reponame := strings.TrimSuffix(rr[1], ".git")
|
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
|
||||||
|
|
||||||
// LowerCase and trim the repoPath as that's how they are stored.
|
// LowerCase and trim the repoPath as that's how they are stored.
|
||||||
// This should be done after splitting the repoPath into username and reponame
|
// This should be done after splitting the repoPath into username and reponame
|
||||||
// so that username and reponame are not affected.
|
// so that username and reponame are not affected.
|
||||||
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
||||||
|
|
||||||
if alphaDashDotPattern.MatchString(reponame) {
|
if !repo.IsValidSSHAccessRepoName(reponame) {
|
||||||
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,21 +258,22 @@ func runServ(c *cli.Context) error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedCommands.Contains(verb) {
|
verb, lfsVerb := sshCmdArgs[0], ""
|
||||||
if allowedCommandsLfs.Contains(verb) {
|
if !git.IsAllowedVerbForServe(verb) {
|
||||||
|
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
|
||||||
|
}
|
||||||
|
|
||||||
|
if git.IsAllowedVerbForServeLfs(verb) {
|
||||||
if !setting.LFS.StartServer {
|
if !setting.LFS.StartServer {
|
||||||
return fail(ctx, "LFS Server is not enabled", "")
|
return fail(ctx, "LFS Server is not enabled", "")
|
||||||
}
|
}
|
||||||
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
|
||||||
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
||||||
}
|
}
|
||||||
if len(words) > 2 {
|
if len(sshCmdArgs) > 2 {
|
||||||
lfsVerb = words[2]
|
lfsVerb = sshCmdArgs[2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
|
|
||||||
}
|
|
||||||
|
|
||||||
requestedMode := getAccessMode(verb, lfsVerb)
|
requestedMode := getAccessMode(verb, lfsVerb)
|
||||||
|
|
||||||
@ -310,7 +283,7 @@ func runServ(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LFS SSH protocol
|
// LFS SSH protocol
|
||||||
if verb == verbLfsTransfer {
|
if verb == git.CmdVerbLfsTransfer {
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -319,7 +292,7 @@ func runServ(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LFS token authentication
|
// LFS token authentication
|
||||||
if verb == verbLfsAuthenticate {
|
if verb == git.CmdVerbLfsAuthenticate {
|
||||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
||||||
|
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
||||||
|
@ -67,15 +67,15 @@ type globalVarsStruct struct {
|
|||||||
validRepoNamePattern *regexp.Regexp
|
validRepoNamePattern *regexp.Regexp
|
||||||
invalidRepoNamePattern *regexp.Regexp
|
invalidRepoNamePattern *regexp.Regexp
|
||||||
reservedRepoNames []string
|
reservedRepoNames []string
|
||||||
reservedRepoPatterns []string
|
reservedRepoNamePatterns []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||||
return &globalVarsStruct{
|
return &globalVarsStruct{
|
||||||
validRepoNamePattern: regexp.MustCompile(`[-.\w]+`),
|
validRepoNamePattern: regexp.MustCompile(`^[-.\w]+$`),
|
||||||
invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
|
invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
|
||||||
reservedRepoNames: []string{".", "..", "-"},
|
reservedRepoNames: []string{".", "..", "-"},
|
||||||
reservedRepoPatterns: []string{"*.git", "*.wiki", "*.rss", "*.atom"},
|
reservedRepoNamePatterns: []string{"*.wiki", "*.git", "*.rss", "*.atom"},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -86,7 +86,16 @@ func IsUsableRepoName(name string) error {
|
|||||||
// Note: usually this error is normally caught up earlier in the UI
|
// Note: usually this error is normally caught up earlier in the UI
|
||||||
return db.ErrNameCharsNotAllowed{Name: name}
|
return db.ErrNameCharsNotAllowed{Name: name}
|
||||||
}
|
}
|
||||||
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoPatterns, name)
|
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidSSHAccessRepoName is like IsUsableRepoName, but it allows "*.wiki" because wiki repo needs to be accessed in SSH code
|
||||||
|
func IsValidSSHAccessRepoName(name string) bool {
|
||||||
|
vars := globalVars()
|
||||||
|
if !vars.validRepoNamePattern.MatchString(name) || vars.invalidRepoNamePattern.MatchString(name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns[1:], name) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrustModelType defines the types of trust model for this repository
|
// TrustModelType defines the types of trust model for this repository
|
||||||
|
@ -216,8 +216,23 @@ func TestIsUsableRepoName(t *testing.T) {
|
|||||||
|
|
||||||
assert.Error(t, IsUsableRepoName("-"))
|
assert.Error(t, IsUsableRepoName("-"))
|
||||||
assert.Error(t, IsUsableRepoName("🌞"))
|
assert.Error(t, IsUsableRepoName("🌞"))
|
||||||
|
assert.Error(t, IsUsableRepoName("the/repo"))
|
||||||
assert.Error(t, IsUsableRepoName("the..repo"))
|
assert.Error(t, IsUsableRepoName("the..repo"))
|
||||||
assert.Error(t, IsUsableRepoName("foo.wiki"))
|
assert.Error(t, IsUsableRepoName("foo.wiki"))
|
||||||
assert.Error(t, IsUsableRepoName("foo.git"))
|
assert.Error(t, IsUsableRepoName("foo.git"))
|
||||||
assert.Error(t, IsUsableRepoName("foo.RSS"))
|
assert.Error(t, IsUsableRepoName("foo.RSS"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsValidSSHAccessRepoName(t *testing.T) {
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName("a"))
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName("-1_."))
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName(".profile"))
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName("foo.wiki"))
|
||||||
|
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("-"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("🌞"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("the/repo"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("the..repo"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("foo.git"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("foo.RSS"))
|
||||||
|
}
|
||||||
|
36
modules/git/cmdverb.go
Normal file
36
modules/git/cmdverb.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
const (
|
||||||
|
CmdVerbUploadPack = "git-upload-pack"
|
||||||
|
CmdVerbUploadArchive = "git-upload-archive"
|
||||||
|
CmdVerbReceivePack = "git-receive-pack"
|
||||||
|
CmdVerbLfsAuthenticate = "git-lfs-authenticate"
|
||||||
|
CmdVerbLfsTransfer = "git-lfs-transfer"
|
||||||
|
|
||||||
|
CmdSubVerbLfsUpload = "upload"
|
||||||
|
CmdSubVerbLfsDownload = "download"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsAllowedVerbForServe(verb string) bool {
|
||||||
|
switch verb {
|
||||||
|
case CmdVerbUploadPack,
|
||||||
|
CmdVerbUploadArchive,
|
||||||
|
CmdVerbReceivePack,
|
||||||
|
CmdVerbLfsAuthenticate,
|
||||||
|
CmdVerbLfsTransfer:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAllowedVerbForServeLfs(verb string) bool {
|
||||||
|
switch verb {
|
||||||
|
case CmdVerbLfsAuthenticate,
|
||||||
|
CmdVerbLfsTransfer:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -46,18 +46,16 @@ type ServCommandResults struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServCommand preps for a serv call
|
// ServCommand preps for a serv call
|
||||||
func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verbs ...string) (*ServCommandResults, ResponseExtra) {
|
func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verb, lfsVerb string) (*ServCommandResults, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
|
||||||
keyID,
|
keyID,
|
||||||
url.PathEscape(ownerName),
|
url.PathEscape(ownerName),
|
||||||
url.PathEscape(repoName),
|
url.PathEscape(repoName),
|
||||||
mode,
|
mode,
|
||||||
)
|
)
|
||||||
for _, verb := range verbs {
|
|
||||||
if verb != "" {
|
|
||||||
reqURL += "&verb=" + url.QueryEscape(verb)
|
reqURL += "&verb=" + url.QueryEscape(verb)
|
||||||
}
|
// reqURL += "&lfs_verb=" + url.QueryEscape(lfsVerb) // TODO: actually there is no use of this parameter. In the future, the URL construction should be more flexible
|
||||||
}
|
_ = lfsVerb
|
||||||
req := newInternalRequestAPI(ctx, reqURL, "GET")
|
req := newInternalRequestAPI(ctx, reqURL, "GET")
|
||||||
return requestJSONResp(req, &ServCommandResults{})
|
return requestJSONResp(req, &ServCommandResults{})
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
ownerName := ctx.PathParam("owner")
|
ownerName := ctx.PathParam("owner")
|
||||||
repoName := ctx.PathParam("repo")
|
repoName := ctx.PathParam("repo")
|
||||||
mode := perm.AccessMode(ctx.FormInt("mode"))
|
mode := perm.AccessMode(ctx.FormInt("mode"))
|
||||||
|
verb := ctx.FormString("verb")
|
||||||
|
|
||||||
// Set the basic parts of the results to return
|
// Set the basic parts of the results to return
|
||||||
results := private.ServCommandResults{
|
results := private.ServCommandResults{
|
||||||
@ -295,8 +296,11 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Because of the special ref "refs/for" we will need to delay write permission check
|
// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
|
||||||
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
|
// 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 {
|
||||||
mode = perm.AccessModeRead
|
mode = perm.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -30,6 +32,7 @@ import (
|
|||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -105,7 +108,12 @@ func testGitGeneral(t *testing.T, u *url.URL) {
|
|||||||
|
|
||||||
// Setup key the user ssh key
|
// Setup key the user ssh key
|
||||||
withKeyFile(t, keyname, func(keyFile string) {
|
withKeyFile(t, keyname, func(keyFile string) {
|
||||||
t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile))
|
var keyID int64
|
||||||
|
t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
|
||||||
|
keyID = key.ID
|
||||||
|
}))
|
||||||
|
assert.NotZero(t, keyID)
|
||||||
|
t.Run("LFSAccessTest", doSSHLFSAccessTest(sshContext, keyID))
|
||||||
|
|
||||||
// Setup remote link
|
// Setup remote link
|
||||||
// TODO: get url from api
|
// TODO: get url from api
|
||||||
@ -136,6 +144,36 @@ func testGitGeneral(t *testing.T, u *url.URL) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doSSHLFSAccessTest(_ APITestContext, keyID int64) func(*testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
sshCommand := os.Getenv("GIT_SSH_COMMAND") // it is set in withKeyFile
|
||||||
|
sshCmdParts, err := shellquote.Split(sshCommand) // and parse the ssh command to construct some mocked arguments
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("User2AccessOwned", func(t *testing.T) {
|
||||||
|
sshCmdUser2Self := append(slices.Clone(sshCmdParts),
|
||||||
|
"-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
|
||||||
|
"git-lfs-authenticate", "user2/repo1.git", "upload", // accessible to own repo
|
||||||
|
)
|
||||||
|
cmd := exec.CommandContext(t.Context(), sshCmdUser2Self[0], sshCmdUser2Self[1:]...)
|
||||||
|
_, err := cmd.Output()
|
||||||
|
assert.NoError(t, err) // accessible, no error
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("User2AccessOther", func(t *testing.T) {
|
||||||
|
sshCmdUser2Other := append(slices.Clone(sshCmdParts),
|
||||||
|
"-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
|
||||||
|
"git-lfs-authenticate", "user5/repo4.git", "upload", // inaccessible to other's (user5/repo4)
|
||||||
|
)
|
||||||
|
cmd := exec.CommandContext(t.Context(), sshCmdUser2Other[0], sshCmdUser2Other[1:]...)
|
||||||
|
_, err := cmd.Output()
|
||||||
|
var errExit *exec.ExitError
|
||||||
|
require.ErrorAs(t, err, &errExit) // inaccessible, error
|
||||||
|
assert.Contains(t, string(errExit.Stderr), fmt.Sprintf("User: 2:user2 with Key: %d:test-key is not authorized to write to user5/repo4.", keyID))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ensureAnonymousClone(t *testing.T, u *url.URL) {
|
func ensureAnonymousClone(t *testing.T, u *url.URL) {
|
||||||
dstLocalPath := t.TempDir()
|
dstLocalPath := t.TempDir()
|
||||||
t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
|
t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user