mirror of
https://github.com/go-gitea/gitea
synced 2026-02-06 18:53:01 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcce96c08d | ||
|
|
885f2b89d6 | ||
|
|
57ce10c0ca | ||
|
|
25785041e7 | ||
|
|
ff3d11034d | ||
|
|
750649c1ef | ||
|
|
eb95bbc1fd |
@@ -20,6 +20,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Add more routes to the "expensive" list (#36290)
|
||||
* Make "commit statuses" API accept slashes in "ref" (#36264) (#36275)
|
||||
* BUGFIXES
|
||||
* Fix git http service handling #36396
|
||||
* Fix markdown newline handling during IME composition (#36421) #36424
|
||||
* Fix missing repository id when migrating release attachments (#36389)
|
||||
* Fix bug when compare in the pull request (#36363) (#36372)
|
||||
|
||||
@@ -2,7 +2,7 @@ module code.gitea.io/gitea
|
||||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.5
|
||||
toolchain go1.25.7
|
||||
|
||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||
// But some CAs use negative serial number, just relax the check. related:
|
||||
|
||||
@@ -43,13 +43,15 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
|
||||
|
||||
existing := &PackageBlob{}
|
||||
|
||||
has, err := e.Where(builder.Eq{
|
||||
hashCond := builder.Eq{
|
||||
"size": pb.Size,
|
||||
"hash_md5": pb.HashMD5,
|
||||
"hash_sha1": pb.HashSHA1,
|
||||
"hash_sha256": pb.HashSHA256,
|
||||
"hash_sha512": pb.HashSHA512,
|
||||
}).Get(existing)
|
||||
}
|
||||
|
||||
has, err := e.Where(hashCond).Get(existing)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -57,6 +59,11 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
|
||||
return existing, true, nil
|
||||
}
|
||||
if _, err = e.Insert(pb); err != nil {
|
||||
// Handle race condition: another request may have inserted the same blob
|
||||
// between our SELECT and INSERT. Retry the SELECT to get the existing blob.
|
||||
if has, _ = e.Where(hashCond).Get(existing); has {
|
||||
return existing, true, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
return pb, false, nil
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func TestGetOrInsertBlobConcurrent(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testBlob := PackageBlob{
|
||||
Size: 123,
|
||||
HashMD5: "md5",
|
||||
HashSHA1: "sha1",
|
||||
HashSHA256: "sha256",
|
||||
HashSHA512: "sha512",
|
||||
}
|
||||
|
||||
const numGoroutines = 3
|
||||
var wg errgroup.Group
|
||||
results := make([]*PackageBlob, numGoroutines)
|
||||
existed := make([]bool, numGoroutines)
|
||||
for idx := range numGoroutines {
|
||||
wg.Go(func() error {
|
||||
blob := testBlob // Create a copy of the test blob for each goroutine
|
||||
var err error
|
||||
results[idx], existed[idx], err = GetOrInsertBlob(t.Context(), &blob)
|
||||
return err
|
||||
})
|
||||
}
|
||||
require.NoError(t, wg.Wait())
|
||||
|
||||
// then: all GetOrInsertBlob succeeds with the same blob ID, and only one indicates it did not exist before
|
||||
existedCount := 0
|
||||
assert.NotNil(t, results[0])
|
||||
for i := range numGoroutines {
|
||||
assert.Equal(t, results[0].ID, results[i].ID)
|
||||
if existed[i] {
|
||||
existedCount++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, numGoroutines-1, existedCount)
|
||||
}
|
||||
@@ -26,9 +26,18 @@ import (
|
||||
|
||||
// saveAsPackageBlob creates a package blob from an upload
|
||||
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
||||
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam // PackageBlob is never used
|
||||
// There will be concurrent uploading for the same blob, so it needs a global lock per blob hash
|
||||
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam //returned PackageBlob is never used
|
||||
pb := packages_service.NewPackageBlob(hsr)
|
||||
err := globallock.LockAndDo(ctx, "container-blob:"+pb.HashSHA256, func(ctx context.Context) error {
|
||||
var err error
|
||||
pb, err = saveAsPackageBlobInternal(ctx, hsr, pci, pb)
|
||||
return err
|
||||
})
|
||||
return pb, err
|
||||
}
|
||||
|
||||
func saveAsPackageBlobInternal(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo, pb *packages_model.PackageBlob) (*packages_model.PackageBlob, error) {
|
||||
exists := false
|
||||
|
||||
contentStore := packages_module.NewContentStore()
|
||||
@@ -67,7 +76,7 @@ func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader
|
||||
return createFileForBlob(ctx, uploadVersion, pb)
|
||||
})
|
||||
if err != nil {
|
||||
if !exists {
|
||||
if !exists && pb != nil { // pb can be nil if GetOrInsertBlob failed
|
||||
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
||||
log.Error("Error deleting package blob from content store: %v", err)
|
||||
}
|
||||
|
||||
@@ -230,8 +230,7 @@ func AuthorizeOAuth(ctx *context.Context) {
|
||||
|
||||
// pkce support
|
||||
switch form.CodeChallengeMethod {
|
||||
case "S256":
|
||||
case "plain":
|
||||
case "S256", "plain":
|
||||
if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
|
||||
@@ -63,10 +63,10 @@ func NewBlobUploader(ctx context.Context, id string) (*BlobUploader, error) {
|
||||
}
|
||||
|
||||
return &BlobUploader{
|
||||
model,
|
||||
hash,
|
||||
f,
|
||||
false,
|
||||
PackageBlobUpload: model,
|
||||
MultiHasher: hash,
|
||||
file: f,
|
||||
reading: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
<a class="author text black tw-font-semibold muted"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>{{if .IsTypeBot}}<span class="ui basic label tw-p-1 tw-align-baseline">bot</span>{{end}}
|
||||
<a class="author text black tw-font-semibold muted"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>{{if .IsTypeBot}} <span class="ui basic label tw-p-1 tw-align-baseline">bot</span>{{end}}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -95,6 +96,45 @@ func TestAuthorizeShow(t *testing.T) {
|
||||
htmlDoc.GetCSRF()
|
||||
}
|
||||
|
||||
func TestAuthorizeGrantS256RequiresVerifier(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
ctx := loginUser(t, "user4")
|
||||
codeChallenge := "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg"
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=S256&code_challenge="+url.QueryEscape(codeChallenge))
|
||||
resp := ctx.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
AssertHTMLElement(t, htmlDoc, "#authorize-app", true)
|
||||
|
||||
grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"state": "thestate",
|
||||
"scope": "",
|
||||
"nonce": "",
|
||||
"redirect_uri": "a",
|
||||
"granted": "true",
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
u, err := grantResp.Result().Location()
|
||||
assert.NoError(t, err)
|
||||
code := u.Query().Get("code")
|
||||
assert.NotEmpty(t, code)
|
||||
|
||||
accessReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "a",
|
||||
"code": code,
|
||||
})
|
||||
accessResp := MakeRequest(t, accessReq, http.StatusBadRequest)
|
||||
parsedError := new(oauth2_provider.AccessTokenError)
|
||||
assert.NoError(t, json.Unmarshal(accessResp.Body.Bytes(), parsedError))
|
||||
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
||||
assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription)
|
||||
}
|
||||
|
||||
func TestAuthorizeRedirectWithExistingGrant(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https%3A%2F%2Fexample.com%2Fxyzzy&response_type=code&state=thestate")
|
||||
|
||||
@@ -35,7 +35,7 @@ const baseOptions: MonacoOpts = {
|
||||
renderLineHighlight: 'all',
|
||||
renderLineHighlightOnlyWhenFocus: true,
|
||||
rulers: [],
|
||||
scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6},
|
||||
scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6, alwaysConsumeMouseWheel: false},
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user