mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Test if container blob is accessible before mounting (#22759)
related #16865 This PR adds an accessibility check before mounting container blobs. --------- Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
		@@ -5,11 +5,18 @@ package packages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/perm"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/builder"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ErrPackageBlobNotExist indicates a package blob not exist error
 | 
					// ErrPackageBlobNotExist indicates a package blob not exist error
 | 
				
			||||||
@@ -98,3 +105,42 @@ func GetTotalUnreferencedBlobSize(ctx context.Context) (int64, error) {
 | 
				
			|||||||
		Where("package_file.id IS NULL").
 | 
							Where("package_file.id IS NULL").
 | 
				
			||||||
		SumInt(&PackageBlob{}, "size")
 | 
							SumInt(&PackageBlob{}, "size")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsBlobAccessibleForUser tests if the user has access to the blob
 | 
				
			||||||
 | 
					func IsBlobAccessibleForUser(ctx context.Context, blobID int64, user *user_model.User) (bool, error) {
 | 
				
			||||||
 | 
						if user.IsAdmin {
 | 
				
			||||||
 | 
							return true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						maxTeamAuthorize := builder.
 | 
				
			||||||
 | 
							Select("max(team.authorize)").
 | 
				
			||||||
 | 
							From("team").
 | 
				
			||||||
 | 
							InnerJoin("team_user", "team_user.team_id = team.id").
 | 
				
			||||||
 | 
							Where(builder.Eq{"team_user.uid": user.ID}.And(builder.Expr("team_user.org_id = `user`.id")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						maxTeamUnitAccessMode := builder.
 | 
				
			||||||
 | 
							Select("max(team_unit.access_mode)").
 | 
				
			||||||
 | 
							From("team").
 | 
				
			||||||
 | 
							InnerJoin("team_user", "team_user.team_id = team.id").
 | 
				
			||||||
 | 
							InnerJoin("team_unit", "team_unit.team_id = team.id").
 | 
				
			||||||
 | 
							Where(builder.Eq{"team_user.uid": user.ID, "team_unit.type": unit.TypePackages}.And(builder.Expr("team_user.org_id = `user`.id")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cond := builder.Eq{"package_blob.id": blobID}.And(
 | 
				
			||||||
 | 
							// owner = user
 | 
				
			||||||
 | 
							builder.Eq{"`user`.id": user.ID}.
 | 
				
			||||||
 | 
								// user can see owner
 | 
				
			||||||
 | 
								Or(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}.Or(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})).
 | 
				
			||||||
 | 
								// owner is an organization and user has access to it
 | 
				
			||||||
 | 
								Or(builder.Eq{"`user`.type": user_model.UserTypeOrganization}.
 | 
				
			||||||
 | 
									And(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamAuthorize}.Or(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamUnitAccessMode}))),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return db.GetEngine(ctx).
 | 
				
			||||||
 | 
							Table("package_blob").
 | 
				
			||||||
 | 
							Join("INNER", "package_file", "package_file.blob_id = package_blob.id").
 | 
				
			||||||
 | 
							Join("INNER", "package_version", "package_version.id = package_file.version_id").
 | 
				
			||||||
 | 
							Join("INNER", "package", "package.id = package_version.package_id").
 | 
				
			||||||
 | 
							Join("INNER", "user", "`user`.id = package.owner_id").
 | 
				
			||||||
 | 
							Where(cond).
 | 
				
			||||||
 | 
							Exist(&PackageBlob{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -203,17 +203,25 @@ func InitiateUploadBlob(ctx *context.Context) {
 | 
				
			|||||||
			Digest:     mount,
 | 
								Digest:     mount,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		if blob != nil {
 | 
							if blob != nil {
 | 
				
			||||||
			if err := mountBlob(&packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}, blob.Blob); err != nil {
 | 
								accessible, err := packages_model.IsBlobAccessibleForUser(ctx, blob.Blob.ID, ctx.Doer)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
				apiError(ctx, http.StatusInternalServerError, err)
 | 
									apiError(ctx, http.StatusInternalServerError, err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			setResponseHeaders(ctx.Resp, &containerHeaders{
 | 
								if accessible {
 | 
				
			||||||
				Location:      fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
 | 
									if err := mountBlob(&packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}, blob.Blob); err != nil {
 | 
				
			||||||
				ContentDigest: mount,
 | 
										apiError(ctx, http.StatusInternalServerError, err)
 | 
				
			||||||
				Status:        http.StatusCreated,
 | 
										return
 | 
				
			||||||
			})
 | 
									}
 | 
				
			||||||
			return
 | 
					
 | 
				
			||||||
 | 
									setResponseHeaders(ctx.Resp, &containerHeaders{
 | 
				
			||||||
 | 
										Location:      fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
 | 
				
			||||||
 | 
										ContentDigest: mount,
 | 
				
			||||||
 | 
										Status:        http.StatusCreated,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@ func TestPackageContainer(t *testing.T) {
 | 
				
			|||||||
	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
						user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
				
			||||||
	session := loginUser(t, user.Name)
 | 
						session := loginUser(t, user.Name)
 | 
				
			||||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
 | 
						token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
 | 
				
			||||||
 | 
						privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	has := func(l packages_model.PackagePropertyList, name string) bool {
 | 
						has := func(l packages_model.PackagePropertyList, name string) bool {
 | 
				
			||||||
		for _, pp := range l {
 | 
							for _, pp := range l {
 | 
				
			||||||
@@ -262,7 +263,16 @@ func TestPackageContainer(t *testing.T) {
 | 
				
			|||||||
			t.Run("UploadBlob/Mount", func(t *testing.T) {
 | 
								t.Run("UploadBlob/Mount", func(t *testing.T) {
 | 
				
			||||||
				defer tests.PrintCurrentTest(t)()
 | 
									defer tests.PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
 | 
									privateBlobDigest := "sha256:6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d"
 | 
				
			||||||
 | 
									req := NewRequestWithBody(t, "POST", fmt.Sprintf("%sv2/%s/%s/blobs/uploads?digest=%s", setting.AppURL, privateUser.Name, image, privateBlobDigest), strings.NewReader("gitea"))
 | 
				
			||||||
 | 
									req = AddBasicAuthHeader(req, privateUser.Name)
 | 
				
			||||||
 | 
									MakeRequest(t, req, http.StatusCreated)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
 | 
				
			||||||
 | 
									addTokenAuthHeader(req, userToken)
 | 
				
			||||||
 | 
									MakeRequest(t, req, http.StatusAccepted)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, privateBlobDigest))
 | 
				
			||||||
				addTokenAuthHeader(req, userToken)
 | 
									addTokenAuthHeader(req, userToken)
 | 
				
			||||||
				MakeRequest(t, req, http.StatusAccepted)
 | 
									MakeRequest(t, req, http.StatusAccepted)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user