mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Check quota limits for container uploads (#22450)
The test coverage has revealed that container packages were not checked against the quota limits.
This commit is contained in:
		@@ -26,14 +26,18 @@ var uploadVersionMutex sync.Mutex
 | 
			
		||||
 | 
			
		||||
// 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(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) {
 | 
			
		||||
func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) {
 | 
			
		||||
	if err := packages_service.CheckSizeQuotaExceeded(db.DefaultContext, pci.Creator, pci.Owner, packages_model.TypeContainer, hsr.Size()); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pb := packages_service.NewPackageBlob(hsr)
 | 
			
		||||
 | 
			
		||||
	exists := false
 | 
			
		||||
 | 
			
		||||
	contentStore := packages_module.NewContentStore()
 | 
			
		||||
 | 
			
		||||
	uploadVersion, err := getOrCreateUploadVersion(pi)
 | 
			
		||||
	uploadVersion, err := getOrCreateUploadVersion(&pci.PackageInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -227,8 +227,22 @@ func InitiateUploadBlob(ctx *context.Context) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := saveAsPackageBlob(buf, &packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}); err != nil {
 | 
			
		||||
		if _, err := saveAsPackageBlob(
 | 
			
		||||
			buf,
 | 
			
		||||
			&packages_service.PackageCreationInfo{
 | 
			
		||||
				PackageInfo: packages_service.PackageInfo{
 | 
			
		||||
					Owner: ctx.Package.Owner,
 | 
			
		||||
					Name:  image,
 | 
			
		||||
				},
 | 
			
		||||
				Creator: ctx.Doer,
 | 
			
		||||
			},
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			switch err {
 | 
			
		||||
			case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
 | 
			
		||||
				apiError(ctx, http.StatusForbidden, err)
 | 
			
		||||
			default:
 | 
			
		||||
				apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -358,8 +372,22 @@ func EndUploadBlob(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := saveAsPackageBlob(uploader, &packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}); err != nil {
 | 
			
		||||
	if _, err := saveAsPackageBlob(
 | 
			
		||||
		uploader,
 | 
			
		||||
		&packages_service.PackageCreationInfo{
 | 
			
		||||
			PackageInfo: packages_service.PackageInfo{
 | 
			
		||||
				Owner: ctx.Package.Owner,
 | 
			
		||||
				Name:  image,
 | 
			
		||||
			},
 | 
			
		||||
			Creator: ctx.Doer,
 | 
			
		||||
		},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		switch err {
 | 
			
		||||
		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
 | 
			
		||||
			apiError(ctx, http.StatusForbidden, err)
 | 
			
		||||
		default:
 | 
			
		||||
			apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -526,8 +554,13 @@ func UploadManifest(ctx *context.Context) {
 | 
			
		||||
		} else if errors.Is(err, container_model.ErrContainerBlobNotExist) {
 | 
			
		||||
			apiErrorDefined(ctx, errBlobUnknown)
 | 
			
		||||
		} else {
 | 
			
		||||
			switch err {
 | 
			
		||||
			case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
 | 
			
		||||
				apiError(ctx, http.StatusForbidden, err)
 | 
			
		||||
			default:
 | 
			
		||||
				apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -327,6 +327,10 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mci.IsTagged {
 | 
			
		||||
		if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
 | 
			
		||||
			log.Error("Error setting package version property: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -173,7 +173,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if versionCreated {
 | 
			
		||||
		if err := checkCountQuotaExceeded(ctx, pvci.Creator, pvci.Owner); err != nil {
 | 
			
		||||
		if err := CheckCountQuotaExceeded(ctx, pvci.Creator, pvci.Owner); err != nil {
 | 
			
		||||
			return nil, false, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -240,7 +240,7 @@ func NewPackageBlob(hsr packages_module.HashedSizeReader) *packages_model.Packag
 | 
			
		||||
func addFileToPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) {
 | 
			
		||||
	log.Trace("Adding package file: %v, %s", pv.ID, pfci.Filename)
 | 
			
		||||
 | 
			
		||||
	if err := checkSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil {
 | 
			
		||||
	if err := CheckSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil {
 | 
			
		||||
		return nil, nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -302,7 +302,9 @@ func addFileToPackageVersion(ctx context.Context, pv *packages_model.PackageVers
 | 
			
		||||
	return pf, pb, !exists, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User) error {
 | 
			
		||||
// CheckCountQuotaExceeded checks if the owner has more than the allowed packages
 | 
			
		||||
// The check is skipped if the doer is an admin.
 | 
			
		||||
func CheckCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User) error {
 | 
			
		||||
	if doer.IsAdmin {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -324,7 +326,9 @@ func checkCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, packageType packages_model.Type, uploadSize int64) error {
 | 
			
		||||
// CheckSizeQuotaExceeded checks if the upload size is bigger than the allowed size
 | 
			
		||||
// The check is skipped if the doer is an admin.
 | 
			
		||||
func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, packageType packages_model.Type, uploadSize int64) error {
 | 
			
		||||
	if doer.IsAdmin {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,10 @@ package integration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -171,11 +173,17 @@ func TestPackageAccess(t *testing.T) {
 | 
			
		||||
func TestPackageQuota(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	limitTotalOwnerCount, limitTotalOwnerSize, limitSizeGeneric := setting.Packages.LimitTotalOwnerCount, setting.Packages.LimitTotalOwnerSize, setting.Packages.LimitSizeGeneric
 | 
			
		||||
	limitTotalOwnerCount, limitTotalOwnerSize := setting.Packages.LimitTotalOwnerCount, setting.Packages.LimitTotalOwnerSize
 | 
			
		||||
 | 
			
		||||
	// Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload.
 | 
			
		||||
	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 | 
			
		||||
	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
 | 
			
		||||
 | 
			
		||||
	t.Run("Common", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		limitSizeGeneric := setting.Packages.LimitSizeGeneric
 | 
			
		||||
 | 
			
		||||
		uploadPackage := func(doer *user_model.User, version string, expectedStatus int) {
 | 
			
		||||
			url := fmt.Sprintf("/api/packages/%s/generic/test-package/%s/file.bin", user.Name, version)
 | 
			
		||||
			req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
 | 
			
		||||
@@ -183,8 +191,6 @@ func TestPackageQuota(t *testing.T) {
 | 
			
		||||
			MakeRequest(t, req, expectedStatus)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	// Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload.
 | 
			
		||||
 | 
			
		||||
		setting.Packages.LimitTotalOwnerCount = 0
 | 
			
		||||
		uploadPackage(user, "1.0", http.StatusForbidden)
 | 
			
		||||
		uploadPackage(admin, "1.0", http.StatusCreated)
 | 
			
		||||
@@ -199,6 +205,30 @@ func TestPackageQuota(t *testing.T) {
 | 
			
		||||
		uploadPackage(user, "1.2", http.StatusForbidden)
 | 
			
		||||
		uploadPackage(admin, "1.2", http.StatusCreated)
 | 
			
		||||
		setting.Packages.LimitSizeGeneric = limitSizeGeneric
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Container", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		limitSizeContainer := setting.Packages.LimitSizeContainer
 | 
			
		||||
 | 
			
		||||
		uploadBlob := func(doer *user_model.User, data string, expectedStatus int) {
 | 
			
		||||
			url := fmt.Sprintf("/v2/%s/quota-test/blobs/uploads?digest=sha256:%x", user.Name, sha256.Sum256([]byte(data)))
 | 
			
		||||
			req := NewRequestWithBody(t, "POST", url, strings.NewReader(data))
 | 
			
		||||
			AddBasicAuthHeader(req, doer.Name)
 | 
			
		||||
			MakeRequest(t, req, expectedStatus)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		setting.Packages.LimitTotalOwnerSize = 0
 | 
			
		||||
		uploadBlob(user, "2", http.StatusForbidden)
 | 
			
		||||
		uploadBlob(admin, "2", http.StatusCreated)
 | 
			
		||||
		setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize
 | 
			
		||||
 | 
			
		||||
		setting.Packages.LimitSizeContainer = 0
 | 
			
		||||
		uploadBlob(user, "3", http.StatusForbidden)
 | 
			
		||||
		uploadBlob(admin, "3", http.StatusCreated)
 | 
			
		||||
		setting.Packages.LimitSizeContainer = limitSizeContainer
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPackageCleanup(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user