Make NuGet service index publicly accessible (#21242)

Addition to #20734, Fixes #20717

The `/index.json` endpoint needs to be accessible even if the registry
is private. The NuGet client uses this endpoint without
authentification.

The old fix only works if the NuGet cli is used with `--source <name>`
but not with `--source <url>/index.json`.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
tokarchuk/v1.18
KN4CK3R 2 years ago committed by GitHub
parent cca189ef97
commit 0c8ce71188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 54
      routers/api/packages/api.go
  2. 80
      tests/integration/api_packages_nuget_test.go

@ -69,7 +69,7 @@ func Routes(ctx gocontext.Context) *web.Route {
r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata) r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata)
r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile) r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile)
r.Put("", reqPackageAccess(perm.AccessModeWrite), composer.UploadPackage) r.Put("", reqPackageAccess(perm.AccessModeWrite), composer.UploadPackage)
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/conan", func() { r.Group("/conan", func() {
r.Group("/v1", func() { r.Group("/v1", func() {
r.Get("/ping", conan.Ping) r.Get("/ping", conan.Ping)
@ -157,7 +157,7 @@ func Routes(ctx gocontext.Context) *web.Route {
}, conan.ExtractPathParameters) }, conan.ExtractPathParameters)
}) })
}) })
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/generic", func() { r.Group("/generic", func() {
r.Group("/{packagename}/{packageversion}", func() { r.Group("/{packagename}/{packageversion}", func() {
r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage) r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage)
@ -169,33 +169,35 @@ func Routes(ctx gocontext.Context) *web.Route {
}, reqPackageAccess(perm.AccessModeWrite)) }, reqPackageAccess(perm.AccessModeWrite))
}) })
}) })
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/helm", func() { r.Group("/helm", func() {
r.Get("/index.yaml", helm.Index) r.Get("/index.yaml", helm.Index)
r.Get("/{filename}", helm.DownloadPackageFile) r.Get("/{filename}", helm.DownloadPackageFile)
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage) r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/maven", func() { r.Group("/maven", func() {
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile) r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
r.Get("/*", maven.DownloadPackageFile) r.Get("/*", maven.DownloadPackageFile)
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/nuget", func() { r.Group("/nuget", func() {
r.Get("/index.json", nuget.ServiceIndex) r.Get("/index.json", nuget.ServiceIndex) // Needs to be unauthenticated for the NuGet client.
r.Get("/query", nuget.SearchService)
r.Group("/registration/{id}", func() {
r.Get("/index.json", nuget.RegistrationIndex)
r.Get("/{version}", nuget.RegistrationLeaf)
})
r.Group("/package/{id}", func() {
r.Get("/index.json", nuget.EnumeratePackageVersions)
r.Get("/{version}/{filename}", nuget.DownloadPackageFile)
})
r.Group("", func() { r.Group("", func() {
r.Put("/", nuget.UploadPackage) r.Get("/query", nuget.SearchService)
r.Put("/symbolpackage", nuget.UploadSymbolPackage) r.Group("/registration/{id}", func() {
r.Delete("/{id}/{version}", nuget.DeletePackage) r.Get("/index.json", nuget.RegistrationIndex)
}, reqPackageAccess(perm.AccessModeWrite)) r.Get("/{version}", nuget.RegistrationLeaf)
r.Get("/symbols/{filename}/{guid:[0-9a-f]{32}}FFFFFFFF/{filename2}", nuget.DownloadSymbolFile) })
r.Group("/package/{id}", func() {
r.Get("/index.json", nuget.EnumeratePackageVersions)
r.Get("/{version}/{filename}", nuget.DownloadPackageFile)
})
r.Group("", func() {
r.Put("/", nuget.UploadPackage)
r.Put("/symbolpackage", nuget.UploadSymbolPackage)
r.Delete("/{id}/{version}", nuget.DeletePackage)
}, reqPackageAccess(perm.AccessModeWrite))
r.Get("/symbols/{filename}/{guid:[0-9a-f]{32}}FFFFFFFF/{filename2}", nuget.DownloadSymbolFile)
}, reqPackageAccess(perm.AccessModeRead))
}) })
r.Group("/npm", func() { r.Group("/npm", func() {
r.Group("/@{scope}/{id}", func() { r.Group("/@{scope}/{id}", func() {
@ -239,7 +241,7 @@ func Routes(ctx gocontext.Context) *web.Route {
r.Group("/-/v1/search", func() { r.Group("/-/v1/search", func() {
r.Get("", npm.PackageSearch) r.Get("", npm.PackageSearch)
}) })
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/pub", func() { r.Group("/pub", func() {
r.Group("/api/packages", func() { r.Group("/api/packages", func() {
r.Group("/versions/new", func() { r.Group("/versions/new", func() {
@ -253,12 +255,12 @@ func Routes(ctx gocontext.Context) *web.Route {
r.Get("/{version}", pub.PackageVersionMetadata) r.Get("/{version}", pub.PackageVersionMetadata)
}) })
}) })
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/pypi", func() { r.Group("/pypi", func() {
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile) r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile) r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
r.Get("/simple/{id}", pypi.PackageMetadata) r.Get("/simple/{id}", pypi.PackageMetadata)
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/rubygems", func() { r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages) r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest) r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@ -269,7 +271,7 @@ func Routes(ctx gocontext.Context) *web.Route {
r.Post("/", rubygems.UploadPackageFile) r.Post("/", rubygems.UploadPackageFile)
r.Delete("/yank", rubygems.DeletePackage) r.Delete("/yank", rubygems.DeletePackage)
}, reqPackageAccess(perm.AccessModeWrite)) }, reqPackageAccess(perm.AccessModeWrite))
}) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/vagrant", func() { r.Group("/vagrant", func() {
r.Group("/authenticate", func() { r.Group("/authenticate", func() {
r.Get("", vagrant.CheckAuthenticate) r.Get("", vagrant.CheckAuthenticate)
@ -282,8 +284,8 @@ func Routes(ctx gocontext.Context) *web.Route {
r.Put("", reqPackageAccess(perm.AccessModeWrite), vagrant.UploadPackageFile) r.Put("", reqPackageAccess(perm.AccessModeWrite), vagrant.UploadPackageFile)
}) })
}) })
}) }, reqPackageAccess(perm.AccessModeRead))
}, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) }, context_service.UserAssignmentWeb(), context.PackageAssignment())
return r return r
} }

@ -19,6 +19,7 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
nuget_module "code.gitea.io/gitea/modules/packages/nuget" nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/packages/nuget" "code.gitea.io/gitea/routers/api/packages/nuget"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -66,39 +67,58 @@ func TestPackageNuGet(t *testing.T) {
t.Run("ServiceIndex", func(t *testing.T) { t.Run("ServiceIndex", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) cases := []struct {
req = addNuGetAPIKeyHeader(req, token) Owner string
resp := MakeRequest(t, req, http.StatusOK) UseBasicAuth bool
UseTokenAuth bool
}{
{privateUser.Name, false, false},
{privateUser.Name, true, false},
{privateUser.Name, false, true},
{user.Name, false, false},
{user.Name, true, false},
{user.Name, false, true},
}
var result nuget.ServiceIndexResponse for _, c := range cases {
DecodeJSON(t, resp, &result) url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
assert.Equal(t, "3.0.0", result.Version) req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
assert.NotEmpty(t, result.Resources) if c.UseBasicAuth {
req = AddBasicAuthHeader(req, user.Name)
root := setting.AppURL + url[1:] } else if c.UseTokenAuth {
for _, r := range result.Resources { req = addNuGetAPIKeyHeader(req, token)
switch r.Type { }
case "SearchQueryService": resp := MakeRequest(t, req, http.StatusOK)
fallthrough
case "SearchQueryService/3.0.0-beta": var result nuget.ServiceIndexResponse
fallthrough DecodeJSON(t, resp, &result)
case "SearchQueryService/3.0.0-rc":
assert.Equal(t, root+"/query", r.ID) assert.Equal(t, "3.0.0", result.Version)
case "RegistrationsBaseUrl": assert.NotEmpty(t, result.Resources)
fallthrough
case "RegistrationsBaseUrl/3.0.0-beta": root := setting.AppURL + url[1:]
fallthrough for _, r := range result.Resources {
case "RegistrationsBaseUrl/3.0.0-rc": switch r.Type {
assert.Equal(t, root+"/registration", r.ID) case "SearchQueryService":
case "PackageBaseAddress/3.0.0": fallthrough
assert.Equal(t, root+"/package", r.ID) case "SearchQueryService/3.0.0-beta":
case "PackagePublish/2.0.0": fallthrough
assert.Equal(t, root, r.ID) case "SearchQueryService/3.0.0-rc":
assert.Equal(t, root+"/query", r.ID)
case "RegistrationsBaseUrl":
fallthrough
case "RegistrationsBaseUrl/3.0.0-beta":
fallthrough
case "RegistrationsBaseUrl/3.0.0-rc":
assert.Equal(t, root+"/registration", r.ID)
case "PackageBaseAddress/3.0.0":
assert.Equal(t, root+"/package", r.ID)
case "PackagePublish/2.0.0":
assert.Equal(t, root, r.ID)
}
} }
} }
}) })

Loading…
Cancel
Save