feat: vendor gitea 1.16.2

This commit is contained in:
2026-06-02 08:36:01 +00:00
parent 247cd60f69
commit 54798b4615
2145 changed files with 162965 additions and 126447 deletions
@@ -187,13 +187,6 @@ func PostLockHandler(ctx *context.Context) {
})
return
}
if git_model.IsErrLFSUnauthorizedAction(err) {
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`)
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
Message: "You must have push access to create locks : " + err.Error(),
})
return
}
log.Error("Unable to CreateLFSLock in repository %-v at %s for user %-v: Error: %v", repository, req.Path, ctx.Doer, err)
ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
Message: "internal server error : Internal Server Error",
@@ -317,13 +310,6 @@ func UnLockHandler(ctx *context.Context) {
lock, err := git_model.DeleteLFSLockByID(ctx, ctx.PathParamInt64("lid"), repository, ctx.Doer, req.Force)
if err != nil {
if git_model.IsErrLFSUnauthorizedAction(err) {
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`)
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
Message: "You must have push access to delete locks : " + err.Error(),
})
return
}
log.Error("Unable to DeleteLFSLockByID[%d] by user %-v with force %t: Error: %v", ctx.PathParamInt64("lid"), ctx.Doer, req.Force, err)
ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
Message: "unable to delete lock : Internal Server Error",
+42 -43
View File
@@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"io"
"maps"
"net/http"
"net/url"
"regexp"
@@ -19,7 +18,6 @@ import (
"strings"
"time"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
git_model "code.gitea.io/gitea/models/git"
perm_model "code.gitea.io/gitea/models/perm"
@@ -34,6 +32,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
"github.com/golang-jwt/jwt/v5"
@@ -44,7 +43,6 @@ type requestContext struct {
User string
Repo string
Authorization string
Method string
RepoGitURL string
}
@@ -175,7 +173,7 @@ func DownloadHandler(ctx *context.Context) {
if len(filename) > 0 {
decodedFilename, err := base64.RawURLEncoding.DecodeString(filename)
if err == nil {
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+string(decodedFilename)+"\"")
ctx.Resp.Header().Set("Content-Disposition", httplib.EncodeContentDispositionAttachment(string(decodedFilename)))
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
}
}
@@ -429,7 +427,6 @@ func getRequestContext(ctx *context.Context) *requestContext {
User: ownerName,
Repo: repoName,
Authorization: ctx.Req.Header.Get("Authorization"),
Method: ctx.Req.Method,
RepoGitURL: httplib.GuessCurrentAppURL(ctx) + url.PathEscape(ownerName) + "/" + url.PathEscape(repoName+".git"),
}
}
@@ -488,40 +485,33 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa
rep.Error = err
} else {
rep.Actions = make(map[string]*lfs_module.Link)
header := make(map[string]string)
if len(rc.Authorization) > 0 {
header["Authorization"] = rc.Authorization
}
if download {
var link *lfs_module.Link
if setting.LFS.Storage.ServeDirect() {
// If we have a signed url (S3, object storage), redirect to this directly.
u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid, rc.Method, nil)
// DO NOT USE the http POST method coming from the lfs batch endpoint
u, err := storage.LFS.ServeDirectURL(pointer.RelativePath(), pointer.Oid, http.MethodGet, nil)
if u != nil && err == nil {
// Presigned url does not need the Authorization header
// https://github.com/go-gitea/gitea/issues/21525
delete(header, "Authorization")
link = &lfs_module.Link{Href: u.String(), Header: header}
link = lfs_module.NewLink(u.String()) // Presigned url does not need the Authorization header
}
}
if link == nil {
link = &lfs_module.Link{Href: rc.DownloadLink(pointer), Header: header}
link = lfs_module.NewLink(rc.DownloadLink(pointer)).WithHeader("Authorization", rc.Authorization)
}
rep.Actions["download"] = link
}
if upload {
rep.Actions["upload"] = &lfs_module.Link{Href: rc.UploadLink(pointer), Header: header}
// Set Transfer-Encoding header to enable chunked uploads. Required by git-lfs client to do chunked transfer.
// See: https://github.com/git-lfs/git-lfs/blob/main/tq/basic_upload.go#L58-59
rep.Actions["upload"] = lfs_module.NewLink(rc.UploadLink(pointer)).
WithHeader("Authorization", rc.Authorization).
WithHeader("Transfer-Encoding", "chunked")
verifyHeader := make(map[string]string)
maps.Copy(verifyHeader, header)
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
verifyHeader["Accept"] = lfs_module.AcceptHeader
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer), Header: verifyHeader}
// "Accept" header is the workaround for git-lfs < 2.8.0 (before 2019).
// This workaround could be removed in the future: https://github.com/git-lfs/git-lfs/issues/3662
rep.Actions["verify"] = lfs_module.NewLink(rc.VerifyLink(pointer)).
WithHeader("Authorization", rc.Authorization).
WithHeader("Accept", lfs_module.AcceptHeader)
}
}
return rep
@@ -551,35 +541,32 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho
accessMode = perm_model.AccessModeWrite
}
if ctx.Data["IsActionsToken"] == true {
taskID := ctx.Data["ActionsTaskID"].(int64)
task, err := actions_model.GetTaskByID(ctx, taskID)
if taskID, ok := user_model.GetActionsUserTaskID(ctx.Doer); ok {
perm, err := access_model.GetActionsUserRepoPermission(ctx, repository, ctx.Doer, taskID)
if err != nil {
log.Error("Unable to GetTaskByID for task[%d] Error: %v", taskID, err)
log.Error("Unable to GetActionsUserRepoPermission for task[%d] Error: %v", taskID, err)
return false
}
if task.RepoID != repository.ID {
return false
}
if task.IsForkPullRequest {
return accessMode <= perm_model.AccessModeRead
}
return accessMode <= perm_model.AccessModeWrite
return perm.CanAccess(accessMode, unit.TypeCode)
}
// ctx.IsSigned is unnecessary here, this will be checked in perm.CanAccess
perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer)
// it works for both anonymous request and signed-in user, then perm.CanAccess will do the permission check
perm, err := access_model.GetDoerRepoPermission(ctx, repository, ctx.Doer)
if err != nil {
log.Error("Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v", ctx.Doer, repository, err)
log.Error("Unable to GetDoerRepoPermission for user %-v in repo %-v Error: %v", ctx.Doer, repository, err)
return false
}
canRead := perm.CanAccess(accessMode, unit.TypeCode)
if canRead && (!requireSigned || ctx.IsSigned) {
canAccess := perm.CanAccess(accessMode, unit.TypeCode)
// if it doesn't require sign-in and anonymous user has access, return true
// if the user is already signed in (for example: by session auth method), and the doer can access, return true
if canAccess && (!requireSigned || ctx.IsSigned) {
return true
}
// now, either sign-in is required or the ctx.Doer cannot access, check the LFS token
// however, "ctx.Doer exists but cannot access then check LFS token" should not really happen:
// * why a request can be sent with both valid user session and valid LFS token then use LFS token to access?
user, err := parseToken(ctx, authorization, repository, accessMode)
if err != nil {
// Most of these are Warn level - the true internal server errors are logged in parseToken already
@@ -619,6 +606,18 @@ func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repo
log.Error("Unable to GetUserById[%d]: Error: %v", claims.UserID, err)
return nil, err
}
if !u.IsActive || u.ProhibitLogin {
return nil, util.NewPermissionDeniedErrorf("not allowed to access any repository")
}
perm, err := access_model.GetDoerRepoPermission(ctx, target, u)
if err != nil {
log.Error("Unable to GetDoerRepoPermission for user[%d] repo[%d]: %v", claims.UserID, target.ID, err)
return nil, err
}
if !perm.CanAccess(mode, unit.TypeCode) {
return nil, util.NewPermissionDeniedErrorf("no permission to access the repository")
}
return u, nil
}
@@ -7,9 +7,11 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/models/db"
perm_model "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
@@ -22,11 +24,15 @@ func TestMain(m *testing.M) {
func TestAuthenticate(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx, _ := contexttest.MockContext(t, "/")
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
token2, _ := GetLFSAuthTokenWithBearer(AuthTokenOptions{Op: "download", UserID: 2, RepoID: 1})
_, token2, _ = strings.Cut(token2, " ")
ctx, _ := contexttest.MockContext(t, "/")
getUserToken := func(op string, userID int64, repo *repo_model.Repository) string {
s, _ := GetLFSAuthTokenWithBearer(AuthTokenOptions{Op: op, UserID: userID, RepoID: repo.ID})
_, token, _ := strings.Cut(s, " ")
return token
}
t.Run("handleLFSToken", func(t *testing.T) {
u, err := handleLFSToken(ctx, "", repo1, perm_model.AccessModeRead)
@@ -37,15 +43,62 @@ func TestAuthenticate(t *testing.T) {
require.Error(t, err)
assert.Nil(t, u)
u, err = handleLFSToken(ctx, token2, repo1, perm_model.AccessModeRead)
u, err = handleLFSToken(ctx, getUserToken("download", 2, repo1), repo1, perm_model.AccessModeRead)
require.NoError(t, err)
assert.EqualValues(t, 2, u.ID)
})
t.Run("authenticate", func(t *testing.T) {
const prefixBearer = "Bearer "
token := getUserToken("download", 2, repo1)
assert.False(t, authenticate(ctx, repo1, "", true, false))
assert.False(t, authenticate(ctx, repo1, prefixBearer+"invalid", true, false))
assert.True(t, authenticate(ctx, repo1, prefixBearer+token2, true, false))
assert.True(t, authenticate(ctx, repo1, prefixBearer+token, true, false))
})
handleLFSTokenTestPerm := func(op string, userID int64, repo *repo_model.Repository, accessMode perm_model.AccessMode) error {
token := getUserToken(op, userID, repo)
u, err := handleLFSToken(ctx, token, repo, accessMode)
if err == nil {
assert.Equal(t, userID, u.ID)
}
return err
}
t.Run("handleLFSToken blocks prohibited users", func(t *testing.T) {
user37 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 37})
// prohibited user
assert.True(t, user37.ProhibitLogin)
err := handleLFSTokenTestPerm("download", 37, repo1, perm_model.AccessModeRead)
assert.ErrorContains(t, err, "not allowed to access any repository")
// normal user
_, _ = db.GetEngine(t.Context()).ID(37).Cols("prohibit_login").Update(&user_model.User{ProhibitLogin: false})
err = handleLFSTokenTestPerm("download", 37, repo1, perm_model.AccessModeRead)
assert.NoError(t, err)
// inactive user
_, _ = db.GetEngine(t.Context()).ID(37).Cols("is_active").Update(&user_model.User{IsActive: false})
err = handleLFSTokenTestPerm("download", 37, repo1, perm_model.AccessModeRead)
assert.ErrorContains(t, err, "not allowed to access any repository")
})
t.Run("handleLFSToken blocks users without repo access", func(t *testing.T) {
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
err := handleLFSTokenTestPerm("download", 10, repo2, perm_model.AccessModeRead)
assert.ErrorContains(t, err, "no permission to access the repository")
})
t.Run("handleLFSToken requires write access for uploads", func(t *testing.T) {
err := handleLFSTokenTestPerm("download", 10, repo1, perm_model.AccessModeRead)
assert.NoError(t, err)
err = handleLFSTokenTestPerm("upload", 10, repo1, perm_model.AccessModeWrite)
assert.ErrorContains(t, err, "no permission to access the repository")
})
t.Run("handleLFSToken allows writes for authorized users", func(t *testing.T) {
err := handleLFSTokenTestPerm("upload", 2, repo1, perm_model.AccessModeWrite)
assert.NoError(t, err)
})
}