From 0c8ce71188cae1d59380a213816a22bce48691db 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 --- routers/api/packages/api.go | 54 +++++++------- tests/integration/api_packages_nuget_test.go | 78 ++++++++++++-------- 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index cb9b3b78ab..3354fe12d4 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -69,7 +69,7 @@ func Routes(ctx gocontext.Context) *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) @@ -157,7 +157,7 @@ func Routes(ctx gocontext.Context) *web.Route { }, conan.ExtractPathParameters) }) }) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/generic", func() { r.Group("/{packagename}/{packageversion}", func() { r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage) @@ -169,33 +169,35 @@ func Routes(ctx gocontext.Context) *web.Route { }, 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() { @@ -239,7 +241,7 @@ func Routes(ctx gocontext.Context) *web.Route { r.Group("/-/v1/search", func() { r.Get("", npm.PackageSearch) }) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/pub", func() { r.Group("/api/packages", func() { r.Group("/versions/new", func() { @@ -253,12 +255,12 @@ func Routes(ctx gocontext.Context) *web.Route { r.Get("/{version}", pub.PackageVersionMetadata) }) }) - }) + }, 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) @@ -269,7 +271,7 @@ func Routes(ctx gocontext.Context) *web.Route { r.Post("/", rubygems.UploadPackageFile) r.Delete("/yank", rubygems.DeletePackage) }, reqPackageAccess(perm.AccessModeWrite)) - }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/vagrant", func() { r.Group("/authenticate", func() { r.Get("", vagrant.CheckAuthenticate) @@ -282,8 +284,8 @@ func Routes(ctx gocontext.Context) *web.Route { r.Put("", reqPackageAccess(perm.AccessModeWrite), vagrant.UploadPackageFile) }) }) - }) - }, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) + }, reqPackageAccess(perm.AccessModeRead)) + }, context_service.UserAssignmentWeb(), context.PackageAssignment()) return r } diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 87275feb3e..9d53311d35 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/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" "code.gitea.io/gitea/tests" @@ -66,39 +67,58 @@ func TestPackageNuGet(t *testing.T) { t.Run("ServiceIndex", func(t *testing.T) { defer tests.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) + 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) - 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) + 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) + } } } })