2022-03-30 11:42:47 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-03-30 11:42:47 +03:00
2022-09-02 22:18:23 +03:00
package integration
2022-03-30 11:42:47 +03:00
import (
"bytes"
"fmt"
"net/http"
2023-01-29 20:34:29 +03:00
"strings"
2022-03-30 11:42:47 +03:00
"testing"
2022-04-06 04:32:09 +03:00
"time"
2022-03-30 11:42:47 +03:00
2023-01-18 00:46:03 +03:00
auth_model "code.gitea.io/gitea/models/auth"
2022-04-06 04:32:09 +03:00
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
2022-03-30 11:42:47 +03:00
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
2022-11-09 09:34:27 +03:00
"code.gitea.io/gitea/modules/setting"
2022-03-30 11:42:47 +03:00
api "code.gitea.io/gitea/modules/structs"
2023-02-23 17:11:56 +03:00
"code.gitea.io/gitea/modules/util"
2022-04-06 04:32:09 +03:00
packages_service "code.gitea.io/gitea/services/packages"
2023-02-05 13:12:31 +03:00
packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
2022-09-02 22:18:23 +03:00
"code.gitea.io/gitea/tests"
2022-03-30 11:42:47 +03:00
2023-02-22 22:21:46 +03:00
"github.com/minio/sha256-simd"
2022-03-30 11:42:47 +03:00
"github.com/stretchr/testify/assert"
)
func TestPackageAPI ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2022-10-24 22:23:25 +03:00
2022-08-16 05:22:25 +03:00
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 4 } )
2022-03-30 11:42:47 +03:00
session := loginUser ( t , user . Name )
2023-01-18 00:46:03 +03:00
tokenReadPackage := getTokenForLoggedInUser ( t , session , auth_model . AccessTokenScopeReadPackage )
Redesign Scoped Access Tokens (#24767)
## Changes
- Adds the following high level access scopes, each with `read` and
`write` levels:
- `activitypub`
- `admin` (hidden if user is not a site admin)
- `misc`
- `notification`
- `organization`
- `package`
- `issue`
- `repository`
- `user`
- Adds new middleware function `tokenRequiresScopes()` in addition to
`reqToken()`
- `tokenRequiresScopes()` is used for each high-level api section
- _if_ a scoped token is present, checks that the required scope is
included based on the section and HTTP method
- `reqToken()` is used for individual routes
- checks that required authentication is present (but does not check
scope levels as this will already have been handled by
`tokenRequiresScopes()`
- Adds migration to convert old scoped access tokens to the new set of
scopes
- Updates the user interface for scope selection
### User interface example
<img width="903" alt="Screen Shot 2023-05-31 at 1 56 55 PM"
src="https://github.com/go-gitea/gitea/assets/23248839/654766ec-2143-4f59-9037-3b51600e32f3">
<img width="917" alt="Screen Shot 2023-05-31 at 1 56 43 PM"
src="https://github.com/go-gitea/gitea/assets/23248839/1ad64081-012c-4a73-b393-66b30352654c">
## tokenRequiresScopes Design Decision
- `tokenRequiresScopes()` was added to more reliably cover api routes.
For an incoming request, this function uses the given scope category
(say `AccessTokenScopeCategoryOrganization`) and the HTTP method (say
`DELETE`) and verifies that any scoped tokens in use include
`delete:organization`.
- `reqToken()` is used to enforce auth for individual routes that
require it. If a scoped token is not present for a request,
`tokenRequiresScopes()` will not return an error
## TODO
- [x] Alphabetize scope categories
- [x] Change 'public repos only' to a radio button (private vs public).
Also expand this to organizations
- [X] Disable token creation if no scopes selected. Alternatively, show
warning
- [x] `reqToken()` is missing from many `POST/DELETE` routes in the api.
`tokenRequiresScopes()` only checks that a given token has the correct
scope, `reqToken()` must be used to check that a token (or some other
auth) is present.
- _This should be addressed in this PR_
- [x] The migration should be reviewed very carefully in order to
minimize access changes to existing user tokens.
- _This should be addressed in this PR_
- [x] Link to api to swagger documentation, clarify what
read/write/delete levels correspond to
- [x] Review cases where more than one scope is needed as this directly
deviates from the api definition.
- _This should be addressed in this PR_
- For example:
```go
m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser,
auth_model.AccessTokenScopeCategoryOrganization),
context_service.UserAssignmentAPI())
```
## Future improvements
- [ ] Add required scopes to swagger documentation
- [ ] Redesign `reqToken()` to be opt-out rather than opt-in
- [ ] Subdivide scopes like `repository`
- [ ] Once a token is created, if it has no scopes, we should display
text instead of an empty bullet point
- [ ] If the 'public repos only' option is selected, should read
categories be selected by default
Closes #24501
Closes #24799
Co-authored-by: Jonathan Tran <jon@allspice.io>
Co-authored-by: Kyle D <kdumontnu@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-04 21:57:16 +03:00
tokenDeletePackage := getTokenForLoggedInUser ( t , session , auth_model . AccessTokenScopeWritePackage )
2022-03-30 11:42:47 +03:00
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 )
2023-12-22 02:59:59 +03:00
req := NewRequestWithBody ( t , "PUT" , url , bytes . NewReader ( [ ] byte { } ) ) .
AddBasicAuth ( user . Name )
2022-03-30 11:42:47 +03:00
MakeRequest ( t , req , http . StatusCreated )
t . Run ( "ListPackages" , func ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrintCurrentTest ( t ) ( )
2022-03-30 11:42:47 +03:00
2023-12-22 02:59:59 +03:00
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s" , user . Name ) ) .
AddTokenAuth ( tokenReadPackage )
2022-03-30 11:42:47 +03:00
resp := MakeRequest ( t , req , http . StatusOK )
var apiPackages [ ] * api . Package
DecodeJSON ( t , resp , & apiPackages )
assert . Len ( t , apiPackages , 1 )
2022-04-06 04:32:09 +03:00
assert . Equal ( t , string ( packages_model . TypeGeneric ) , apiPackages [ 0 ] . Type )
2022-03-30 11:42:47 +03:00
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 ) {
2022-09-02 22:18:23 +03:00
defer tests . PrintCurrentTest ( t ) ( )
2022-03-30 11:42:47 +03:00
2023-12-22 02:59:59 +03:00
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s/dummy/%s/%s" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenReadPackage )
2022-03-30 11:42:47 +03:00
MakeRequest ( t , req , http . StatusNotFound )
2023-12-22 02:59:59 +03:00
req = NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s/generic/%s/%s" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenReadPackage )
2022-03-30 11:42:47 +03:00
resp := MakeRequest ( t , req , http . StatusOK )
var p * api . Package
DecodeJSON ( t , resp , & p )
2022-04-06 04:32:09 +03:00
assert . Equal ( t , string ( packages_model . TypeGeneric ) , p . Type )
2022-03-30 11:42:47 +03:00
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 )
2022-05-07 19:21:15 +03:00
t . Run ( "RepositoryLink" , func ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrintCurrentTest ( t ) ( )
2022-05-07 19:21:15 +03:00
p , err := packages_model . GetPackageByName ( db . DefaultContext , user . ID , packages_model . TypeGeneric , packageName )
assert . NoError ( t , err )
// no repository link
2023-12-22 02:59:59 +03:00
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s/generic/%s/%s" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenReadPackage )
2022-05-07 19:21:15 +03:00
resp := MakeRequest ( t , req , http . StatusOK )
var ap1 * api . Package
DecodeJSON ( t , resp , & ap1 )
assert . Nil ( t , ap1 . Repository )
// link to public repository
assert . NoError ( t , packages_model . SetRepositoryLink ( db . DefaultContext , p . ID , 1 ) )
2023-12-22 02:59:59 +03:00
req = NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s/generic/%s/%s" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenReadPackage )
2022-05-07 19:21:15 +03:00
resp = MakeRequest ( t , req , http . StatusOK )
var ap2 * api . Package
DecodeJSON ( t , resp , & ap2 )
assert . NotNil ( t , ap2 . Repository )
assert . EqualValues ( t , 1 , ap2 . Repository . ID )
// link to private repository
assert . NoError ( t , packages_model . SetRepositoryLink ( db . DefaultContext , p . ID , 2 ) )
2023-12-22 02:59:59 +03:00
req = NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s/generic/%s/%s" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenReadPackage )
2022-05-07 19:21:15 +03:00
resp = MakeRequest ( t , req , http . StatusOK )
var ap3 * api . Package
DecodeJSON ( t , resp , & ap3 )
assert . Nil ( t , ap3 . Repository )
assert . NoError ( t , packages_model . UnlinkRepositoryFromAllPackages ( db . DefaultContext , 2 ) )
} )
2022-03-30 11:42:47 +03:00
} )
t . Run ( "ListPackageFiles" , func ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrintCurrentTest ( t ) ( )
2022-03-30 11:42:47 +03:00
2023-12-22 02:59:59 +03:00
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s/dummy/%s/%s/files" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenReadPackage )
2022-03-30 11:42:47 +03:00
MakeRequest ( t , req , http . StatusNotFound )
2023-12-22 02:59:59 +03:00
req = NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s/generic/%s/%s/files" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenReadPackage )
2022-03-30 11:42:47 +03:00
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 ) {
2022-09-02 22:18:23 +03:00
defer tests . PrintCurrentTest ( t ) ( )
2022-03-30 11:42:47 +03:00
2023-12-22 02:59:59 +03:00
req := NewRequest ( t , "DELETE" , fmt . Sprintf ( "/api/v1/packages/%s/dummy/%s/%s" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenDeletePackage )
2022-03-30 11:42:47 +03:00
MakeRequest ( t , req , http . StatusNotFound )
2023-12-22 02:59:59 +03:00
req = NewRequest ( t , "DELETE" , fmt . Sprintf ( "/api/v1/packages/%s/generic/%s/%s" , user . Name , packageName , packageVersion ) ) .
AddTokenAuth ( tokenDeletePackage )
2022-03-30 11:42:47 +03:00
MakeRequest ( t , req , http . StatusNoContent )
} )
}
2022-04-06 04:32:09 +03:00
2022-10-24 22:23:25 +03:00
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 } )
2023-07-09 16:00:07 +03:00
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 )
2022-10-24 22:23:25 +03:00
req := NewRequestWithBody ( t , "PUT" , url , bytes . NewReader ( [ ] byte { 1 } ) )
2023-07-09 16:00:07 +03:00
if doer != nil {
2023-12-22 02:59:59 +03:00
req . AddBasicAuth ( doer . Name )
2023-07-09 16:00:07 +03:00
}
2022-10-24 22:23:25 +03:00
MakeRequest ( t , req , expectedStatus )
}
2023-07-09 16:00:07 +03:00
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 {
2023-12-22 02:59:59 +03:00
req . AddBasicAuth ( doer . Name )
2023-07-09 16:00:07 +03:00
}
MakeRequest ( t , req , expectedStatus )
}
2023-04-06 17:18:29 +03:00
2023-07-09 16:00:07 +03:00
type Target struct {
Owner * user_model . User
ExpectedStatus int
}
2023-04-06 17:18:29 +03:00
2023-07-09 16:00:07 +03:00
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 } ,
} {
2023-12-22 02:59:59 +03:00
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/api/v1/packages/%s" , target . Owner . Name ) ) .
AddTokenAuth ( tokenReadPackage )
2023-07-09 16:00:07 +03:00
MakeRequest ( t , req , target . ExpectedStatus )
}
} )
2022-10-24 22:23:25 +03:00
}
2022-11-09 09:34:27 +03:00
func TestPackageQuota ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
2023-01-29 20:34:29 +03:00
limitTotalOwnerCount , limitTotalOwnerSize := setting . Packages . LimitTotalOwnerCount , setting . Packages . LimitTotalOwnerSize
2022-11-09 09:34:27 +03:00
2023-01-29 20:34:29 +03:00
// Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload.
2022-11-09 09:34:27 +03:00
admin := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 1 } )
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 10 } )
2023-01-29 20:34:29 +03:00
t . Run ( "Common" , func ( t * testing . T ) {
defer tests . PrintCurrentTest ( t ) ( )
2022-11-09 09:34:27 +03:00
2023-01-29 20:34:29 +03:00
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 )
2023-12-22 02:59:59 +03:00
req := NewRequestWithBody ( t , "PUT" , url , bytes . NewReader ( [ ] byte { 1 } ) ) .
AddBasicAuth ( doer . Name )
2023-01-29 20:34:29 +03:00
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
2022-11-09 09:34:27 +03:00
2023-01-29 20:34:29 +03:00
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 ) ) )
2023-12-22 02:59:59 +03:00
req := NewRequestWithBody ( t , "POST" , url , strings . NewReader ( data ) ) .
AddBasicAuth ( doer . Name )
2023-01-29 20:34:29 +03:00
MakeRequest ( t , req , expectedStatus )
}
2022-11-09 09:34:27 +03:00
2023-01-29 20:34:29 +03:00
setting . Packages . LimitTotalOwnerSize = 0
uploadBlob ( user , "2" , http . StatusForbidden )
uploadBlob ( admin , "2" , http . StatusCreated )
setting . Packages . LimitTotalOwnerSize = limitTotalOwnerSize
2022-11-09 09:34:27 +03:00
2023-01-29 20:34:29 +03:00
setting . Packages . LimitSizeContainer = 0
uploadBlob ( user , "3" , http . StatusForbidden )
uploadBlob ( admin , "3" , http . StatusCreated )
setting . Packages . LimitSizeContainer = limitSizeContainer
} )
2022-11-09 09:34:27 +03:00
}
2022-04-06 04:32:09 +03:00
func TestPackageCleanup ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2022-04-06 04:32:09 +03:00
2023-02-23 17:11:56 +03:00
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 2 } )
2022-11-20 17:08:38 +03:00
duration , _ := time . ParseDuration ( "-1h" )
2022-04-06 04:32:09 +03:00
2022-11-20 17:08:38 +03:00
t . Run ( "Common" , func ( t * testing . T ) {
defer tests . PrintCurrentTest ( t ) ( )
2023-02-23 17:11:56 +03:00
// 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 )
2023-12-22 02:59:59 +03:00
req := NewRequestWithBody ( t , "PUT" , url , bytes . NewReader ( data ) ) .
AddBasicAuth ( user . Name )
2023-02-23 17:11:56 +03:00
MakeRequest ( t , req , http . StatusCreated )
2023-12-22 02:59:59 +03:00
req = NewRequest ( t , "DELETE" , url ) .
AddBasicAuth ( user . Name )
2023-02-23 17:11:56 +03:00
MakeRequest ( t , req , http . StatusNoContent )
data , _ = util . CryptoRandomBytes ( 5 )
url = fmt . Sprintf ( "/v2/%s/cleanup-test/blobs/uploads?digest=sha256:%x" , user . Name , sha256 . Sum256 ( data ) )
2023-12-22 02:59:59 +03:00
req = NewRequestWithBody ( t , "POST" , url , bytes . NewReader ( data ) ) .
AddBasicAuth ( user . Name )
2023-02-23 17:11:56 +03:00
MakeRequest ( t , req , http . StatusCreated )
2022-11-20 17:08:38 +03:00
pbs , err := packages_model . FindExpiredUnreferencedBlobs ( db . DefaultContext , duration )
assert . NoError ( t , err )
assert . NotEmpty ( t , pbs )
2022-04-06 04:32:09 +03:00
2023-02-23 17:11:56 +03:00
_ , err = packages_model . GetInternalVersionByNameAndVersion ( db . DefaultContext , user . ID , packages_model . TypeContainer , "cleanup-test" , container_model . UploadVersion )
2022-11-20 17:08:38 +03:00
assert . NoError ( t , err )
2022-04-06 04:32:09 +03:00
2023-08-08 03:46:10 +03:00
err = packages_cleanup_service . CleanupTask ( db . DefaultContext , duration )
2022-11-20 17:08:38 +03:00
assert . NoError ( t , err )
2022-04-06 04:32:09 +03:00
2022-11-20 17:08:38 +03:00
pbs , err = packages_model . FindExpiredUnreferencedBlobs ( db . DefaultContext , duration )
assert . NoError ( t , err )
assert . Empty ( t , pbs )
2023-02-23 17:11:56 +03:00
_ , err = packages_model . GetInternalVersionByNameAndVersion ( db . DefaultContext , user . ID , packages_model . TypeContainer , "cleanup-test" , container_model . UploadVersion )
2022-11-20 17:08:38 +03:00
assert . ErrorIs ( t , err , packages_model . ErrPackageNotExist )
} )
2022-04-06 04:32:09 +03:00
2022-11-20 17:08:38 +03:00
t . Run ( "CleanupRules" , func ( t * testing . T ) {
defer tests . PrintCurrentTest ( t ) ( )
type version struct {
Version string
ShouldExist bool
Created int64
}
cases := [ ] struct {
Name string
Versions [ ] version
Rule * packages_model . PackageCleanupRule
} {
{
Name : "Disabled" ,
Versions : [ ] version {
{ Version : "keep" , ShouldExist : true } ,
} ,
Rule : & packages_model . PackageCleanupRule {
Enabled : false ,
} ,
} ,
{
Name : "KeepCount" ,
Versions : [ ] version {
{ Version : "keep" , ShouldExist : true } ,
{ Version : "v1.0" , ShouldExist : true } ,
{ Version : "test-3" , ShouldExist : false , Created : 1 } ,
{ Version : "test-4" , ShouldExist : false , Created : 1 } ,
} ,
Rule : & packages_model . PackageCleanupRule {
Enabled : true ,
KeepCount : 2 ,
} ,
} ,
{
Name : "KeepPattern" ,
Versions : [ ] version {
{ Version : "keep" , ShouldExist : true } ,
{ Version : "v1.0" , ShouldExist : false } ,
} ,
Rule : & packages_model . PackageCleanupRule {
Enabled : true ,
KeepPattern : "k.+p" ,
} ,
} ,
{
Name : "RemoveDays" ,
Versions : [ ] version {
{ Version : "keep" , ShouldExist : true } ,
{ Version : "v1.0" , ShouldExist : false , Created : 1 } ,
} ,
Rule : & packages_model . PackageCleanupRule {
Enabled : true ,
RemoveDays : 60 ,
} ,
} ,
{
Name : "RemovePattern" ,
Versions : [ ] version {
{ Version : "test" , ShouldExist : true } ,
{ Version : "test-3" , ShouldExist : false } ,
{ Version : "test-4" , ShouldExist : false } ,
} ,
Rule : & packages_model . PackageCleanupRule {
Enabled : true ,
RemovePattern : ` t[e]+st-\d+ ` ,
} ,
} ,
{
Name : "MatchFullName" ,
Versions : [ ] version {
{ Version : "keep" , ShouldExist : true } ,
{ Version : "test" , ShouldExist : false } ,
} ,
Rule : & packages_model . PackageCleanupRule {
Enabled : true ,
RemovePattern : ` package/test|different/keep ` ,
MatchFullName : true ,
} ,
} ,
{
Name : "Mixed" ,
Versions : [ ] version {
{ Version : "keep" , ShouldExist : true , Created : time . Now ( ) . Add ( time . Duration ( 10000 ) ) . Unix ( ) } ,
{ Version : "dummy" , ShouldExist : true , Created : 1 } ,
{ Version : "test-3" , ShouldExist : true } ,
{ Version : "test-4" , ShouldExist : false , Created : 1 } ,
} ,
Rule : & packages_model . PackageCleanupRule {
Enabled : true ,
KeepCount : 1 ,
KeepPattern : ` dummy ` ,
RemoveDays : 7 ,
RemovePattern : ` t[e]+st-\d+ ` ,
} ,
} ,
}
for _ , c := range cases {
t . Run ( c . Name , func ( t * testing . T ) {
defer tests . PrintCurrentTest ( t ) ( )
for _ , v := range c . Versions {
url := fmt . Sprintf ( "/api/packages/%s/generic/package/%s/file.bin" , user . Name , v . Version )
2023-12-22 02:59:59 +03:00
req := NewRequestWithBody ( t , "PUT" , url , bytes . NewReader ( [ ] byte { 1 } ) ) .
AddBasicAuth ( user . Name )
2022-11-20 17:08:38 +03:00
MakeRequest ( t , req , http . StatusCreated )
if v . Created != 0 {
pv , err := packages_model . GetVersionByNameAndVersion ( db . DefaultContext , user . ID , packages_model . TypeGeneric , "package" , v . Version )
assert . NoError ( t , err )
_ , err = db . GetEngine ( db . DefaultContext ) . Exec ( "UPDATE package_version SET created_unix = ? WHERE id = ?" , v . Created , pv . ID )
assert . NoError ( t , err )
}
}
c . Rule . OwnerID = user . ID
c . Rule . Type = packages_model . TypeGeneric
pcr , err := packages_model . InsertCleanupRule ( db . DefaultContext , c . Rule )
assert . NoError ( t , err )
2023-08-08 03:46:10 +03:00
err = packages_cleanup_service . CleanupTask ( db . DefaultContext , duration )
2022-11-20 17:08:38 +03:00
assert . NoError ( t , err )
for _ , v := range c . Versions {
pv , err := packages_model . GetVersionByNameAndVersion ( db . DefaultContext , user . ID , packages_model . TypeGeneric , "package" , v . Version )
if v . ShouldExist {
assert . NoError ( t , err )
err = packages_service . DeletePackageVersionAndReferences ( db . DefaultContext , pv )
assert . NoError ( t , err )
} else {
assert . ErrorIs ( t , err , packages_model . ErrPackageNotExist )
}
}
assert . NoError ( t , packages_model . DeleteCleanupRuleByID ( db . DefaultContext , pcr . ID ) )
} )
}
} )
2022-04-06 04:32:09 +03:00
}