From 5bb0980757a4de2f26e603383e068c587c94fd4f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 24 Sep 2022 17:17:08 +0200 Subject: [PATCH] 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 ` but not with `--source /index.json`. Co-authored-by: wxiaoguang --- integrations/api_packages_nuget_test.go | 80 +++++++++++++++---------- routers/api/packages/api.go | 48 ++++++++------- 2 files changed, 75 insertions(+), 53 deletions(-) diff --git a/integrations/api_packages_nuget_test.go b/integrations/api_packages_nuget_test.go index 06eb485541ef..c79b49381b20 100644 --- a/integrations/api_packages_nuget_test.go +++ b/integrations/api_packages_nuget_test.go @@ -19,6 +19,7 @@ import ( user_model "code.gitea.io/gitea/models/user" nuget_module "code.gitea.io/gitea/modules/packages/nuget" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/packages/nuget" "github.com/stretchr/testify/assert" @@ -65,39 +66,58 @@ func TestPackageNuGet(t *testing.T) { t.Run("ServiceIndex", func(t *testing.T) { defer PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) - req = AddBasicAuthHeader(req, user.Name) - MakeRequest(t, req, http.StatusOK) + privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}) - req = NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) - req = addNuGetAPIKeyHeader(req, token) - resp := MakeRequest(t, req, http.StatusOK) + cases := []struct { + Owner string + 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 - DecodeJSON(t, resp, &result) + for _, c := range cases { + url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) - assert.Equal(t, "3.0.0", result.Version) - assert.NotEmpty(t, result.Resources) - - root := setting.AppURL + url[1:] - for _, r := range result.Resources { - switch r.Type { - case "SearchQueryService": - fallthrough - case "SearchQueryService/3.0.0-beta": - fallthrough - 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) + req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) + if c.UseBasicAuth { + req = AddBasicAuthHeader(req, user.Name) + } else if c.UseTokenAuth { + req = addNuGetAPIKeyHeader(req, token) + } + resp := MakeRequest(t, req, http.StatusOK) + + var result nuget.ServiceIndexResponse + DecodeJSON(t, resp, &result) + + assert.Equal(t, "3.0.0", result.Version) + assert.NotEmpty(t, result.Resources) + + root := setting.AppURL + url[1:] + for _, r := range result.Resources { + switch r.Type { + case "SearchQueryService": + fallthrough + case "SearchQueryService/3.0.0-beta": + fallthrough + 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) + } } } }) diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index b27569aba45c..dd5cf4a4c0ed 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -66,7 +66,7 @@ func Routes() *web.Route { r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata) r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile) r.Put("", reqPackageAccess(perm.AccessModeWrite), composer.UploadPackage) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/conan", func() { r.Group("/v1", func() { r.Get("/ping", conan.Ping) @@ -154,7 +154,7 @@ func Routes() *web.Route { }, conan.ExtractPathParameters) }) }) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/generic", func() { r.Group("/{packagename}/{packageversion}/{filename}", func() { r.Get("", generic.DownloadPackageFile) @@ -163,33 +163,35 @@ func Routes() *web.Route { r.Delete("", generic.DeletePackage) }, reqPackageAccess(perm.AccessModeWrite)) }) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/helm", func() { r.Get("/index.yaml", helm.Index) r.Get("/{filename}", helm.DownloadPackageFile) r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/maven", func() { r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile) r.Get("/*", maven.DownloadPackageFile) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/nuget", func() { - r.Get("/index.json", nuget.ServiceIndex) - 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.Get("/index.json", nuget.ServiceIndex) // Needs to be unauthenticated for the NuGet client. 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) + 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.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("/@{scope}/{id}", func() { @@ -216,12 +218,12 @@ func Routes() *web.Route { r.Delete("", npm.DeletePackageTag) }, reqPackageAccess(perm.AccessModeWrite)) }) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/pypi", func() { r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile) r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile) r.Get("/simple/{id}", pypi.PackageMetadata) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/rubygems", func() { r.Get("/specs.4.8.gz", rubygems.EnumeratePackages) r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest) @@ -233,7 +235,7 @@ func Routes() *web.Route { r.Delete("/yank", rubygems.DeletePackage) }, reqPackageAccess(perm.AccessModeWrite)) }) - }, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) + }, context_service.UserAssignmentWeb(), context.PackageAssignment()) return r }