From 98869ce1420e80987ac5fb58e41667d2fb420df1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 7 May 2026 22:21:18 -0700 Subject: [PATCH 1/5] Add label for private and internal package and fix composor package source permission check --- routers/api/packages/composer/api.go | 21 ++++++--- routers/api/packages/composer/composer.go | 7 ++- templates/package/settings.tmpl | 8 +++- .../package/shared/cleanup_rules/preview.tmpl | 7 ++- templates/package/shared/list.tmpl | 5 ++- templates/package/shared/versionlist.tmpl | 8 +++- templates/package/shared/view.tmpl | 7 ++- .../package/shared/visibility_badge.tmpl | 7 +++ .../integration/api_packages_composer_test.go | 45 +++++++++++++++++++ 9 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 templates/package/shared/visibility_badge.tmpl diff --git a/routers/api/packages/composer/api.go b/routers/api/packages/composer/api.go index a3ea2c2f9a..5c3fcb93c5 100644 --- a/routers/api/packages/composer/api.go +++ b/routers/api/packages/composer/api.go @@ -9,7 +9,9 @@ import ( "time" packages_model "code.gitea.io/gitea/models/packages" + access_model "code.gitea.io/gitea/models/perm/access" composer_module "code.gitea.io/gitea/modules/packages/composer" + "code.gitea.io/gitea/services/context" ) // ServiceIndexResponse contains registry endpoints @@ -91,7 +93,7 @@ type Source struct { Reference string `json:"reference"` } -func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse { +func createPackageMetadataResponse(ctx *context.Context, registryURL string, pds []*packages_model.PackageDescriptor) (*PackageMetadataResponse, error) { versions := make([]*PackageVersionMetadata, 0, len(pds)) for _, pd := range pds { @@ -116,10 +118,17 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac }, } if pd.Repository != nil { - pkg.Source = Source{ - URL: pd.Repository.HTMLURL(), - Type: "git", - Reference: pd.Version.Version, + permission, err := access_model.GetDoerRepoPermission(ctx, pd.Repository, ctx.Doer) + if err != nil { + return nil, err + } + + if permission.HasAnyUnitAccess() { + pkg.Source = Source{ + URL: pd.Repository.HTMLURL(), + Type: "git", + Reference: pd.Version.Version, + } } } @@ -131,5 +140,5 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac Packages: map[string][]*PackageVersionMetadata{ pds[0].Package.Name: versions, }, - } + }, nil } diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index 8eb66ca244..2b124a25a4 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -145,10 +145,15 @@ func PackageMetadata(ctx *context.Context) { return } - resp := createPackageMetadataResponse( + resp, err := createPackageMetadataResponse( + ctx, setting.AppURL+"api/packages/"+ctx.Package.Owner.Name+"/composer", pds, ) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } ctx.JSON(http.StatusOK, resp) } diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index d4241e6541..d5577a8974 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -10,7 +10,13 @@ {{template "user/overview/header" .}} {{end}} {{template "base/alert" .}} -

{{.PackageDescriptor.Package.Name}} / {{ctx.Locale.Tr "repo.settings"}}

+

+ {{.PackageDescriptor.Package.Name}} + + {{template "package/shared/visibility_badge" dict "Package" .PackageDescriptor.Package "Owner" .PackageDescriptor.Owner}} + + / {{ctx.Locale.Tr "repo.settings"}} +

{{ctx.Locale.Tr "packages.settings.link"}}

diff --git a/templates/package/shared/cleanup_rules/preview.tmpl b/templates/package/shared/cleanup_rules/preview.tmpl index 0991a07fbc..15ad94debd 100644 --- a/templates/package/shared/cleanup_rules/preview.tmpl +++ b/templates/package/shared/cleanup_rules/preview.tmpl @@ -18,7 +18,12 @@ {{range .VersionsToRemove}} {{.Package.Type.Name}} - {{.Package.Name}} + + {{.Package.Name}} + + {{template "package/shared/visibility_badge" dict "Package" .Package "Owner" .Owner}} + + {{.Version.Version}} {{.Creator.Name}} {{FileSize .CalculateBlobSize}} diff --git a/templates/package/shared/list.tmpl b/templates/package/shared/list.tmpl index 54e2a2c20e..69bd7acbe8 100644 --- a/templates/package/shared/list.tmpl +++ b/templates/package/shared/list.tmpl @@ -21,7 +21,10 @@
{{.Package.Name}} - {{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}} + + {{template "package/shared/visibility_badge" dict "Package" .Package "Owner" .Owner}} + {{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}} +
{{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}} diff --git a/templates/package/shared/versionlist.tmpl b/templates/package/shared/versionlist.tmpl index 3ef9e790f1..a381f324fa 100644 --- a/templates/package/shared/versionlist.tmpl +++ b/templates/package/shared/versionlist.tmpl @@ -1,4 +1,10 @@ -

{{.PackageDescriptor.Package.Name}} / {{ctx.Locale.Tr "packages.versions"}}

+

+ {{.PackageDescriptor.Package.Name}} + + {{template "package/shared/visibility_badge" dict "Package" .PackageDescriptor.Package "Owner" .PackageDescriptor.Owner}} + + / {{ctx.Locale.Tr "packages.versions"}} +

{{template "shared/search/input" dict "Value" .Query "Placeholder" (ctx.Locale.Tr "search.package_kind")}} diff --git a/templates/package/shared/view.tmpl b/templates/package/shared/view.tmpl index 9e32d5fdc2..5bd093178b 100644 --- a/templates/package/shared/view.tmpl +++ b/templates/package/shared/view.tmpl @@ -1,6 +1,11 @@
{{$packageVersionLink := print $.PackageDescriptor.PackageWebLink "/" (PathEscape .PackageDescriptor.Version.LowerVersion)}} -

{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})

+
+

{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})

+ + {{template "package/shared/visibility_badge" dict "Package" .PackageDescriptor.Package "Owner" .PackageDescriptor.Owner}} + +
{{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}} {{if .HasRepositoryAccess}} diff --git a/templates/package/shared/visibility_badge.tmpl b/templates/package/shared/visibility_badge.tmpl new file mode 100644 index 0000000000..75502bd4b0 --- /dev/null +++ b/templates/package/shared/visibility_badge.tmpl @@ -0,0 +1,7 @@ +{{if .Package.IsInternal}} + {{ctx.Locale.Tr "repo.desc.private"}} +{{else if .Owner.Visibility.IsPrivate}} + {{ctx.Locale.Tr "repo.desc.private"}} +{{else if .Owner.Visibility.IsLimited}} + {{ctx.Locale.Tr "repo.desc.internal"}} +{{end}} diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index a56f2dd39b..963bdba256 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -27,6 +27,7 @@ func TestPackageComposer(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + otherUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) vendorName := "gitea" projectName := "composer-package" @@ -243,5 +244,49 @@ func TestPackageComposer(t *testing.T) { assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL) assert.Equal(t, "git", pkgs[0].Source.Type) assert.Equal(t, packageVersion, pkgs[0].Source.Reference) + + // Private repository links remain visible to callers who can access the repository. + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo2.ID) + assert.NoError(t, err) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) + pkgs = result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Equal(t, repo2.HTMLURL(), pkgs[0].Source.URL) + assert.Equal(t, "git", pkgs[0].Source.Type) + assert.Equal(t, packageVersion, pkgs[0].Source.Reference) + + // Callers without repository access still get the package metadata, but not the private source URL. + req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + AddBasicAuth(otherUser.Name) + resp = MakeRequest(t, req, http.StatusOK) + + result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) + pkgs = result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Empty(t, pkgs[0].Source.URL) + assert.Empty(t, pkgs[0].Source.Type) + assert.Empty(t, pkgs[0].Source.Reference) + }) + + t.Run("WebVisibilityBadge", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + listReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages", user.Name)). + AddBasicAuth(user.Name) + listResp := MakeRequest(t, listReq, http.StatusOK) + listDoc := NewHTMLParser(t, bytes.NewReader(listResp.Body.Bytes())) + assert.Equal(t, 0, listDoc.Find(".item-title .ui.basic.label").Length()) + + viewReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages/composer/%s/%s", user.Name, neturl.PathEscape(packageName), neturl.PathEscape(packageVersion))). + AddBasicAuth(user.Name) + viewResp := MakeRequest(t, viewReq, http.StatusOK) + viewDoc := NewHTMLParser(t, bytes.NewReader(viewResp.Body.Bytes())) + assert.Equal(t, 0, viewDoc.Find(".issue-title-header .ui.basic.label").Length()) }) } From 1895833739e502e1cbd8ef94b790e307cc3e0f7f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 8 May 2026 09:57:53 -0700 Subject: [PATCH 2/5] update description --- options/locale/locale_en-US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index c7ec133e57..29fe931a6f 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3622,7 +3622,7 @@ "packages.terraform.delete.latest": "The latest version of a Terraform state cannot be deleted.", "packages.vagrant.install": "To add a Vagrant box, run the following command:", "packages.settings.link": "Link this package to a repository", - "packages.settings.link.description": "If you link a package with a repository, the package will appear in the repository's package list. Only repositories under the same owner can be linked. Leaving the field empty will remove the link.", + "packages.settings.link.description": "If you link a package with a repository, the package will appear in the repository's package list. This link does not change the package visibility. Only repositories under the same owner can be linked. Leaving the field empty will remove the link.", "packages.settings.link.select": "Select Repository", "packages.settings.link.button": "Update Repository Link", "packages.settings.link.success": "Repository link was successfully updated.", From f82cdc3d6cda68424f0d1b7611b7bdba9f344a34 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 8 May 2026 10:43:31 -0700 Subject: [PATCH 3/5] add a link to change the visibility of package --- options/locale/locale_en-US.json | 3 +++ templates/package/settings.tmpl | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 29fe931a6f..ad421a4032 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3623,6 +3623,9 @@ "packages.vagrant.install": "To add a Vagrant box, run the following command:", "packages.settings.link": "Link this package to a repository", "packages.settings.link.description": "If you link a package with a repository, the package will appear in the repository's package list. This link does not change the package visibility. Only repositories under the same owner can be linked. Leaving the field empty will remove the link.", + "packages.settings.visibility": "Package visibility", + "packages.settings.visibility.inherit": "Package visibility is inherited from the owner and cannot be changed independently here. To change it, update the visibility settings of the user or organization that owns this package.", + "packages.settings.visibility.button": "Change owner visibility", "packages.settings.link.select": "Select Repository", "packages.settings.link.button": "Update Repository Link", "packages.settings.link.success": "Repository link was successfully updated.", diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index d5577a8974..8e7490c469 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -17,6 +17,17 @@ / {{ctx.Locale.Tr "repo.settings"}}

+ {{$visibilitySettingsLink := print AppSubUrl "/user/settings"}} + {{if .ContextUser.IsOrganization}} + {{$visibilitySettingsLink = print .ContextUser.OrganisationLink "/settings"}} + {{end}} +

+ {{ctx.Locale.Tr "packages.settings.visibility"}} +

+
+

{{ctx.Locale.Tr "packages.settings.visibility.inherit"}}

+ {{ctx.Locale.Tr "packages.settings.visibility.button"}} +

{{ctx.Locale.Tr "packages.settings.link"}}

From 2d97484be9746c5aa759fe4d536250f0a83fe996 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 8 May 2026 11:05:14 -0700 Subject: [PATCH 4/5] fix --- routers/api/packages/composer/api.go | 11 +++--- routers/api/packages/composer/composer.go | 6 +--- .../package/shared/cleanup_rules/preview.tmpl | 8 +++-- .../package/shared/visibility_badge.tmpl | 2 +- .../integration/api_packages_composer_test.go | 35 +++++++++++++++++++ 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/routers/api/packages/composer/api.go b/routers/api/packages/composer/api.go index 5c3fcb93c5..0d95ab3ed9 100644 --- a/routers/api/packages/composer/api.go +++ b/routers/api/packages/composer/api.go @@ -10,6 +10,7 @@ import ( packages_model "code.gitea.io/gitea/models/packages" access_model "code.gitea.io/gitea/models/perm/access" + "code.gitea.io/gitea/modules/log" composer_module "code.gitea.io/gitea/modules/packages/composer" "code.gitea.io/gitea/services/context" ) @@ -93,7 +94,7 @@ type Source struct { Reference string `json:"reference"` } -func createPackageMetadataResponse(ctx *context.Context, registryURL string, pds []*packages_model.PackageDescriptor) (*PackageMetadataResponse, error) { +func createPackageMetadataResponse(ctx *context.Context, registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse { versions := make([]*PackageVersionMetadata, 0, len(pds)) for _, pd := range pds { @@ -120,10 +121,8 @@ func createPackageMetadataResponse(ctx *context.Context, registryURL string, pds if pd.Repository != nil { permission, err := access_model.GetDoerRepoPermission(ctx, pd.Repository, ctx.Doer) if err != nil { - return nil, err - } - - if permission.HasAnyUnitAccess() { + log.Error("GetDoerRepoPermission[%d]: %v", pd.Repository.ID, err) + } else if permission.HasAnyUnitAccessOrPublicAccess() { pkg.Source = Source{ URL: pd.Repository.HTMLURL(), Type: "git", @@ -140,5 +139,5 @@ func createPackageMetadataResponse(ctx *context.Context, registryURL string, pds Packages: map[string][]*PackageVersionMetadata{ pds[0].Package.Name: versions, }, - }, nil + } } diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index 2b124a25a4..b18cdc242c 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -145,15 +145,11 @@ func PackageMetadata(ctx *context.Context) { return } - resp, err := createPackageMetadataResponse( + resp := createPackageMetadataResponse( ctx, setting.AppURL+"api/packages/"+ctx.Package.Owner.Name+"/composer", pds, ) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } ctx.JSON(http.StatusOK, resp) } diff --git a/templates/package/shared/cleanup_rules/preview.tmpl b/templates/package/shared/cleanup_rules/preview.tmpl index 15ad94debd..a564e37f93 100644 --- a/templates/package/shared/cleanup_rules/preview.tmpl +++ b/templates/package/shared/cleanup_rules/preview.tmpl @@ -20,9 +20,11 @@ {{.Package.Type.Name}} {{.Package.Name}} - - {{template "package/shared/visibility_badge" dict "Package" .Package "Owner" .Owner}} - + {{if .Owner}} + + {{template "package/shared/visibility_badge" dict "Package" .Package "Owner" .Owner}} + + {{end}} {{.Version.Version}} {{.Creator.Name}} diff --git a/templates/package/shared/visibility_badge.tmpl b/templates/package/shared/visibility_badge.tmpl index 75502bd4b0..0677391afc 100644 --- a/templates/package/shared/visibility_badge.tmpl +++ b/templates/package/shared/visibility_badge.tmpl @@ -1,5 +1,5 @@ {{if .Package.IsInternal}} - {{ctx.Locale.Tr "repo.desc.private"}} + {{ctx.Locale.Tr "repo.desc.internal"}} {{else if .Owner.Visibility.IsPrivate}} {{ctx.Locale.Tr "repo.desc.private"}} {{else if .Owner.Visibility.IsLimited}} diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index 963bdba256..f8af50b070 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -28,6 +28,7 @@ func TestPackageComposer(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) otherUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31}) vendorName := "gitea" projectName := "composer-package" @@ -288,5 +289,39 @@ func TestPackageComposer(t *testing.T) { viewResp := MakeRequest(t, viewReq, http.StatusOK) viewDoc := NewHTMLParser(t, bytes.NewReader(viewResp.Body.Bytes())) assert.Equal(t, 0, viewDoc.Find(".issue-title-header .ui.basic.label").Length()) + + privatePackageName := privateUser.Name + "/private-composer-package" + privatePackageVersion := "1.0.0" + privateContent := test.WriteZipArchive(map[string]string{ + "composer.json": `{ + "name": "` + privatePackageName + `", + "description": "Private Package", + "type": "` + packageType + `", + "license": "` + packageLicense + `", + "authors": [ + { + "name": "` + packageAuthor + `" + } + ] + }`, + }).Bytes() + privateUploadURL := fmt.Sprintf("%sapi/packages/%s/composer?version=%s", setting.AppURL, privateUser.Name, privatePackageVersion) + + uploadReq := NewRequestWithBody(t, "PUT", privateUploadURL, bytes.NewReader(privateContent)). + AddBasicAuth(privateUser.Name) + MakeRequest(t, uploadReq, http.StatusCreated) + privateSession := loginUser(t, privateUser.Name) + + privateListReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages", privateUser.Name)) + privateListResp := privateSession.MakeRequest(t, privateListReq, http.StatusOK) + privateListDoc := NewHTMLParser(t, bytes.NewReader(privateListResp.Body.Bytes())) + assert.Equal(t, 1, privateListDoc.Find(".item-title .ui.basic.label").Length()) + assert.Equal(t, "Private", privateListDoc.Find(".item-title .ui.basic.label").First().Text()) + + privateViewReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages/composer/%s/%s", privateUser.Name, neturl.PathEscape(privatePackageName), neturl.PathEscape(privatePackageVersion))) + privateViewResp := privateSession.MakeRequest(t, privateViewReq, http.StatusOK) + privateViewDoc := NewHTMLParser(t, bytes.NewReader(privateViewResp.Body.Bytes())) + assert.Equal(t, 1, privateViewDoc.Find(".issue-title-header .ui.basic.label").Length()) + assert.Equal(t, "Private", privateViewDoc.Find(".issue-title-header .ui.basic.label").First().Text()) }) } From b9be501c66241d85baf4f8cdf8166eaa6cc99337 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 8 May 2026 12:14:21 -0700 Subject: [PATCH 5/5] Add notices for link package to repository --- options/locale/locale_en-US.json | 5 ++++- templates/package/settings.tmpl | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index ad421a4032..a0c2e086ef 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3622,7 +3622,10 @@ "packages.terraform.delete.latest": "The latest version of a Terraform state cannot be deleted.", "packages.vagrant.install": "To add a Vagrant box, run the following command:", "packages.settings.link": "Link this package to a repository", - "packages.settings.link.description": "If you link a package with a repository, the package will appear in the repository's package list. This link does not change the package visibility. Only repositories under the same owner can be linked. Leaving the field empty will remove the link.", + "packages.settings.link.description": "If you link a package with a repository, the package will appear in the repository's package list.", + "packages.settings.link.notice1": "Only repositories under the same owner can be linked.", + "packages.settings.link.notice2": "Linking a repository does not change the package visibility.", + "packages.settings.link.notice3": "Leaving the field empty will remove the link.", "packages.settings.visibility": "Package visibility", "packages.settings.visibility.inherit": "Package visibility is inherited from the owner and cannot be changed independently here. To change it, update the visibility settings of the user or organization that owns this package.", "packages.settings.visibility.button": "Change owner visibility", diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index 8e7490c469..989d97f2ab 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -33,6 +33,9 @@

{{ctx.Locale.Tr "packages.settings.link.description"}}

+

- {{ctx.Locale.Tr "packages.settings.link.notice1"}}

+

- {{ctx.Locale.Tr "packages.settings.link.notice2"}}

+

- {{ctx.Locale.Tr "packages.settings.link.notice3"}}