Skip to content

Commit 115f40e

Browse files
KN4CK3Rtechknowlogicksilverwind
authored
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>
1 parent 38844e0 commit 115f40e

File tree

3 files changed

+72
-8
lines changed

3 files changed

+72
-8
lines changed

models/packages/package_blob.go

+46
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@ package packages
55

66
import (
77
"context"
8+
"strconv"
89
"time"
910

1011
"code.gitea.io/gitea/models/db"
12+
"code.gitea.io/gitea/models/perm"
13+
"code.gitea.io/gitea/models/unit"
14+
user_model "code.gitea.io/gitea/models/user"
15+
"code.gitea.io/gitea/modules/structs"
1116
"code.gitea.io/gitea/modules/timeutil"
1217
"code.gitea.io/gitea/modules/util"
18+
19+
"xorm.io/builder"
1320
)
1421

1522
// ErrPackageBlobNotExist indicates a package blob not exist error
@@ -98,3 +105,42 @@ func GetTotalUnreferencedBlobSize(ctx context.Context) (int64, error) {
98105
Where("package_file.id IS NULL").
99106
SumInt(&PackageBlob{}, "size")
100107
}
108+
109+
// IsBlobAccessibleForUser tests if the user has access to the blob
110+
func IsBlobAccessibleForUser(ctx context.Context, blobID int64, user *user_model.User) (bool, error) {
111+
if user.IsAdmin {
112+
return true, nil
113+
}
114+
115+
maxTeamAuthorize := builder.
116+
Select("max(team.authorize)").
117+
From("team").
118+
InnerJoin("team_user", "team_user.team_id = team.id").
119+
Where(builder.Eq{"team_user.uid": user.ID}.And(builder.Expr("team_user.org_id = `user`.id")))
120+
121+
maxTeamUnitAccessMode := builder.
122+
Select("max(team_unit.access_mode)").
123+
From("team").
124+
InnerJoin("team_user", "team_user.team_id = team.id").
125+
InnerJoin("team_unit", "team_unit.team_id = team.id").
126+
Where(builder.Eq{"team_user.uid": user.ID, "team_unit.type": unit.TypePackages}.And(builder.Expr("team_user.org_id = `user`.id")))
127+
128+
cond := builder.Eq{"package_blob.id": blobID}.And(
129+
// owner = user
130+
builder.Eq{"`user`.id": user.ID}.
131+
// user can see owner
132+
Or(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}.Or(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})).
133+
// owner is an organization and user has access to it
134+
Or(builder.Eq{"`user`.type": user_model.UserTypeOrganization}.
135+
And(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamAuthorize}.Or(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamUnitAccessMode}))),
136+
)
137+
138+
return db.GetEngine(ctx).
139+
Table("package_blob").
140+
Join("INNER", "package_file", "package_file.blob_id = package_blob.id").
141+
Join("INNER", "package_version", "package_version.id = package_file.version_id").
142+
Join("INNER", "package", "package.id = package_version.package_id").
143+
Join("INNER", "user", "`user`.id = package.owner_id").
144+
Where(cond).
145+
Exist(&PackageBlob{})
146+
}

routers/api/packages/container/container.go

+15-7
Original file line numberDiff line numberDiff line change
@@ -203,17 +203,25 @@ func InitiateUploadBlob(ctx *context.Context) {
203203
Digest: mount,
204204
})
205205
if blob != nil {
206-
if err := mountBlob(&packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}, blob.Blob); err != nil {
206+
accessible, err := packages_model.IsBlobAccessibleForUser(ctx, blob.Blob.ID, ctx.Doer)
207+
if err != nil {
207208
apiError(ctx, http.StatusInternalServerError, err)
208209
return
209210
}
210211

211-
setResponseHeaders(ctx.Resp, &containerHeaders{
212-
Location: fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
213-
ContentDigest: mount,
214-
Status: http.StatusCreated,
215-
})
216-
return
212+
if accessible {
213+
if err := mountBlob(&packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}, blob.Blob); err != nil {
214+
apiError(ctx, http.StatusInternalServerError, err)
215+
return
216+
}
217+
218+
setResponseHeaders(ctx.Resp, &containerHeaders{
219+
Location: fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
220+
ContentDigest: mount,
221+
Status: http.StatusCreated,
222+
})
223+
return
224+
}
217225
}
218226
}
219227

tests/integration/api_packages_container_test.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func TestPackageContainer(t *testing.T) {
3434
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
3535
session := loginUser(t, user.Name)
3636
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
37+
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31})
3738

3839
has := func(l packages_model.PackagePropertyList, name string) bool {
3940
for _, pp := range l {
@@ -262,7 +263,16 @@ func TestPackageContainer(t *testing.T) {
262263
t.Run("UploadBlob/Mount", func(t *testing.T) {
263264
defer tests.PrintCurrentTest(t)()
264265

265-
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
266+
privateBlobDigest := "sha256:6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d"
267+
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%sv2/%s/%s/blobs/uploads?digest=%s", setting.AppURL, privateUser.Name, image, privateBlobDigest), strings.NewReader("gitea"))
268+
req = AddBasicAuthHeader(req, privateUser.Name)
269+
MakeRequest(t, req, http.StatusCreated)
270+
271+
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
272+
addTokenAuthHeader(req, userToken)
273+
MakeRequest(t, req, http.StatusAccepted)
274+
275+
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, privateBlobDigest))
266276
addTokenAuthHeader(req, userToken)
267277
MakeRequest(t, req, http.StatusAccepted)
268278

0 commit comments

Comments
 (0)