675 lines · 23.3 KB · Go
RawDownload
// Copyright 2021 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package integration import (	"bytes"	"crypto/sha256"	"fmt"	"net/http"	"strings"	"testing"	"time" 	auth_model "forgejo.org/models/auth"	"forgejo.org/models/db"	packages_model "forgejo.org/models/packages"	container_model "forgejo.org/models/packages/container"	unit_model "forgejo.org/models/unit"	"forgejo.org/models/unittest"	user_model "forgejo.org/models/user"	"forgejo.org/modules/setting"	api "forgejo.org/modules/structs"	"forgejo.org/modules/util"	packages_service "forgejo.org/services/packages"	packages_cleanup_service "forgejo.org/services/packages/cleanup"	"forgejo.org/tests" 	"github.com/stretchr/testify/assert"	"github.com/stretchr/testify/require") func TestPackageAPI(t *testing.T) {	defer tests.PrepareTestEnv(t)() 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})	session := loginUser(t, user.Name)	tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)	tokenWritePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage) 	packageName := "test-package"	packageVersion := "1.0.3"	filename := "file.bin" 	url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, filename)	req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})).		AddBasicAuth(user.Name)	MakeRequest(t, req, http.StatusCreated) 	t.Run("ListPackages", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s", user.Name))		resp := MakeRequest(t, req, http.StatusOK) 		var apiPackages []*api.Package		DecodeJSON(t, resp, &apiPackages) 		assert.Len(t, apiPackages, 1)		assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type)		assert.Equal(t, packageName, apiPackages[0].Name)		assert.Equal(t, packageVersion, apiPackages[0].Version)		assert.NotNil(t, apiPackages[0].Creator)		assert.Equal(t, user.Name, apiPackages[0].Creator.UserName)	}) 	t.Run("GetPackage", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s", user.Name, packageName, packageVersion))		MakeRequest(t, req, http.StatusNotFound) 		req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion))		resp := MakeRequest(t, req, http.StatusOK) 		var p *api.Package		DecodeJSON(t, resp, &p) 		assert.Equal(t, string(packages_model.TypeGeneric), p.Type)		assert.Equal(t, packageName, p.Name)		assert.Equal(t, packageVersion, p.Version)		assert.NotNil(t, p.Creator)		assert.Equal(t, user.Name, p.Creator.UserName) 		t.Run("RepositoryLink", func(t *testing.T) {			defer tests.PrintCurrentTest(t)() 			p, err := packages_model.GetPackageByName(db.DefaultContext, user.ID, packages_model.TypeGeneric, packageName)			require.NoError(t, err) 			// no repository link			req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).				AddTokenAuth(tokenReadPackage)			resp := MakeRequest(t, req, http.StatusOK) 			var ap1 *api.Package			DecodeJSON(t, resp, &ap1)			assert.Nil(t, ap1.Repository) 			// create a repository			repo, _, f := tests.CreateDeclarativeRepo(t, user, "", []unit_model.Type{unit_model.TypeCode}, nil, nil)			defer f() 			// link to public repository			req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, repo.Name)).AddTokenAuth(tokenWritePackage)			MakeRequest(t, req, http.StatusCreated) 			req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, repo.Name))			MakeRequest(t, req, http.StatusUnauthorized) 			req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).				AddTokenAuth(tokenReadPackage)			resp = MakeRequest(t, req, http.StatusOK) 			var ap2 *api.Package			DecodeJSON(t, resp, &ap2)			assert.NotNil(t, ap2.Repository)			assert.Equal(t, repo.ID, ap2.Repository.ID) 			// link to repository without write access, should fail			req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, "repo3")).AddTokenAuth(tokenWritePackage)			MakeRequest(t, req, http.StatusNotFound) 			// remove link			req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/unlink", user.Name, packageName)).AddTokenAuth(tokenWritePackage)			MakeRequest(t, req, http.StatusNoContent) 			req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/unlink", user.Name, packageName))			MakeRequest(t, req, http.StatusUnauthorized) 			req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).				AddTokenAuth(tokenReadPackage)			resp = MakeRequest(t, req, http.StatusOK) 			var ap3 *api.Package			DecodeJSON(t, resp, &ap3)			assert.Nil(t, ap3.Repository) 			// force link to a repository the currently logged-in user doesn't have access to			privateRepoID := int64(6)			require.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, privateRepoID)) 			req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion))			resp = MakeRequest(t, req, http.StatusOK) 			var ap4 *api.Package			DecodeJSON(t, resp, &ap4)			assert.Nil(t, ap4.Repository) 			require.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, privateRepoID))		})	}) 	t.Run("ListPackageFiles", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s/files", user.Name, packageName, packageVersion)).			AddTokenAuth(tokenReadPackage)		MakeRequest(t, req, http.StatusNotFound) 		req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s/files", user.Name, packageName, packageVersion)).			AddTokenAuth(tokenReadPackage)		resp := MakeRequest(t, req, http.StatusOK) 		var files []*api.PackageFile		DecodeJSON(t, resp, &files) 		assert.Len(t, files, 1)		assert.Equal(t, int64(0), files[0].Size)		assert.Equal(t, filename, files[0].Name)		assert.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", files[0].HashMD5)		assert.Equal(t, "da39a3ee5e6b4b0d3255bfef95601890afd80709", files[0].HashSHA1)		assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", files[0].HashSHA256)		assert.Equal(t, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", files[0].HashSHA512)	}) 	t.Run("DeletePackage", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s", user.Name, packageName, packageVersion)).			AddTokenAuth(tokenWritePackage)		MakeRequest(t, req, http.StatusNotFound) 		req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion))		MakeRequest(t, req, http.StatusUnauthorized) 		req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).			AddTokenAuth(tokenWritePackage)		MakeRequest(t, req, http.StatusNoContent)	})} func TestPackageAccess(t *testing.T) {	defer tests.PrepareTestEnv(t)() 	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})	inactive := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9})	limitedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 33})	privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31})	privateOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) // user has package write access	limitedOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 36}) // user has package write access	publicOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 25})  // user has package read access	privateOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 35})	limitedOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22})	publicOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) 	uploadPackage := func(doer, owner *user_model.User, filename string, expectedStatus int) {		url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/%s.bin", owner.Name, filename)		req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))		if doer != nil {			req.AddBasicAuth(doer.Name)		}		MakeRequest(t, req, expectedStatus)	} 	downloadPackage := func(doer, owner *user_model.User, expectedStatus int) {		url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/admin.bin", owner.Name)		req := NewRequest(t, "GET", url)		if doer != nil {			req.AddBasicAuth(doer.Name)		}		MakeRequest(t, req, expectedStatus)	} 	type Target struct {		Owner          *user_model.User		ExpectedStatus int	} 	t.Run("Upload", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		cases := []struct {			Doer     *user_model.User			Filename string			Targets  []Target		}{			{ // Admins can upload to every owner				Doer:     admin,				Filename: "admin",				Targets: []Target{					{admin, http.StatusCreated},					{inactive, http.StatusCreated},					{user, http.StatusCreated},					{limitedUser, http.StatusCreated},					{privateUser, http.StatusCreated},					{privateOrgMember, http.StatusCreated},					{limitedOrgMember, http.StatusCreated},					{publicOrgMember, http.StatusCreated},					{privateOrgNoMember, http.StatusCreated},					{limitedOrgNoMember, http.StatusCreated},					{publicOrgNoMember, http.StatusCreated},				},			},			{ // Without credentials no upload should be possible				Doer:     nil,				Filename: "nil",				Targets: []Target{					{admin, http.StatusUnauthorized},					{inactive, http.StatusUnauthorized},					{user, http.StatusUnauthorized},					{limitedUser, http.StatusUnauthorized},					{privateUser, http.StatusUnauthorized},					{privateOrgMember, http.StatusUnauthorized},					{limitedOrgMember, http.StatusUnauthorized},					{publicOrgMember, http.StatusUnauthorized},					{privateOrgNoMember, http.StatusUnauthorized},					{limitedOrgNoMember, http.StatusUnauthorized},					{publicOrgNoMember, http.StatusUnauthorized},				},			},			{ // Inactive users can't upload anywhere				Doer:     inactive,				Filename: "inactive",				Targets: []Target{					{admin, http.StatusUnauthorized},					{inactive, http.StatusUnauthorized},					{user, http.StatusUnauthorized},					{limitedUser, http.StatusUnauthorized},					{privateUser, http.StatusUnauthorized},					{privateOrgMember, http.StatusUnauthorized},					{limitedOrgMember, http.StatusUnauthorized},					{publicOrgMember, http.StatusUnauthorized},					{privateOrgNoMember, http.StatusUnauthorized},					{limitedOrgNoMember, http.StatusUnauthorized},					{publicOrgNoMember, http.StatusUnauthorized},				},			},			{ // Normal users can upload to self and orgs in which they are members and have package write access				Doer:     user,				Filename: "user",				Targets: []Target{					{admin, http.StatusUnauthorized},					{inactive, http.StatusUnauthorized},					{user, http.StatusCreated},					{limitedUser, http.StatusUnauthorized},					{privateUser, http.StatusUnauthorized},					{privateOrgMember, http.StatusCreated},					{limitedOrgMember, http.StatusCreated},					{publicOrgMember, http.StatusUnauthorized},					{privateOrgNoMember, http.StatusUnauthorized},					{limitedOrgNoMember, http.StatusUnauthorized},					{publicOrgNoMember, http.StatusUnauthorized},				},			},		} 		for _, c := range cases {			for _, t := range c.Targets {				uploadPackage(c.Doer, t.Owner, c.Filename, t.ExpectedStatus)			}		}	}) 	t.Run("Download", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		cases := []struct {			Doer     *user_model.User			Filename string			Targets  []Target		}{			{ // Admins can access everything				Doer: admin,				Targets: []Target{					{admin, http.StatusOK},					{inactive, http.StatusOK},					{user, http.StatusOK},					{limitedUser, http.StatusOK},					{privateUser, http.StatusOK},					{privateOrgMember, http.StatusOK},					{limitedOrgMember, http.StatusOK},					{publicOrgMember, http.StatusOK},					{privateOrgNoMember, http.StatusOK},					{limitedOrgNoMember, http.StatusOK},					{publicOrgNoMember, http.StatusOK},				},			},			{ // Without credentials only public owners are accessible				Doer: nil,				Targets: []Target{					{admin, http.StatusOK},					{inactive, http.StatusOK},					{user, http.StatusOK},					{limitedUser, http.StatusUnauthorized},					{privateUser, http.StatusUnauthorized},					{privateOrgMember, http.StatusUnauthorized},					{limitedOrgMember, http.StatusUnauthorized},					{publicOrgMember, http.StatusOK},					{privateOrgNoMember, http.StatusUnauthorized},					{limitedOrgNoMember, http.StatusUnauthorized},					{publicOrgNoMember, http.StatusOK},				},			},			{ // Inactive users have no access				Doer: inactive,				Targets: []Target{					{admin, http.StatusUnauthorized},					{inactive, http.StatusUnauthorized},					{user, http.StatusUnauthorized},					{limitedUser, http.StatusUnauthorized},					{privateUser, http.StatusUnauthorized},					{privateOrgMember, http.StatusUnauthorized},					{limitedOrgMember, http.StatusUnauthorized},					{publicOrgMember, http.StatusUnauthorized},					{privateOrgNoMember, http.StatusUnauthorized},					{limitedOrgNoMember, http.StatusUnauthorized},					{publicOrgNoMember, http.StatusUnauthorized},				},			},			{ // Normal users can access self, public or limited users/orgs and private orgs in which they are members				Doer: user,				Targets: []Target{					{admin, http.StatusOK},					{inactive, http.StatusOK},					{user, http.StatusOK},					{limitedUser, http.StatusOK},					{privateUser, http.StatusUnauthorized},					{privateOrgMember, http.StatusOK},					{limitedOrgMember, http.StatusOK},					{publicOrgMember, http.StatusOK},					{privateOrgNoMember, http.StatusUnauthorized},					{limitedOrgNoMember, http.StatusOK},					{publicOrgNoMember, http.StatusOK},				},			},		} 		for _, c := range cases {			for _, target := range c.Targets {				downloadPackage(c.Doer, target.Owner, target.ExpectedStatus)			}		}	}) 	t.Run("API", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		session := loginUser(t, user.Name)		tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) 		for _, target := range []Target{			{admin, http.StatusOK},			{inactive, http.StatusOK},			{user, http.StatusOK},			{limitedUser, http.StatusOK},			{privateUser, http.StatusForbidden},			{privateOrgMember, http.StatusOK},			{limitedOrgMember, http.StatusOK},			{publicOrgMember, http.StatusOK},			{privateOrgNoMember, http.StatusForbidden},			{limitedOrgNoMember, http.StatusOK},			{publicOrgNoMember, http.StatusOK},		} {			req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s", target.Owner.Name)).				AddTokenAuth(tokenReadPackage)			MakeRequest(t, req, target.ExpectedStatus)		}	})} func TestPackageQuota(t *testing.T) {	defer tests.PrepareTestEnv(t)() 	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})).				AddBasicAuth(doer.Name)			MakeRequest(t, req, expectedStatus)		} 		setting.Packages.LimitTotalOwnerCount = 0		uploadPackage(user, "1.0", http.StatusForbidden)		uploadPackage(admin, "1.0", http.StatusCreated)		setting.Packages.LimitTotalOwnerCount = limitTotalOwnerCount 		setting.Packages.LimitTotalOwnerSize = 0		uploadPackage(user, "1.1", http.StatusForbidden)		uploadPackage(admin, "1.1", http.StatusCreated)		setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize 		setting.Packages.LimitSizeGeneric = 0		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)).				AddBasicAuth(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) {	defer tests.PrepareTestEnv(t)() 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 	duration, _ := time.ParseDuration("-1h") 	t.Run("Common", func(t *testing.T) {		defer tests.PrintCurrentTest(t)() 		// Upload and delete a generic package and upload a container blob		data := util.CryptoRandomBytes(5)		url := fmt.Sprintf("/api/packages/%s/generic/cleanup-test/1.1.1/file.bin", user.Name)		req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(data)).			AddBasicAuth(user.Name)		MakeRequest(t, req, http.StatusCreated) 

Lines 1–500 of 675.