Files
gitea/tests/integration/feed_repo_test.go
T
Basavaraj S m 269b116b1b fix(feed): hide release notes in tags feed without releases access (#39944)
The tags feed (`tags.rss` / `tags.atom`) lives in the tags route group,
which only requires code read access, yet it lists every published
release next to the plain tags and includes each release's title and
rendered note. The releases page and `releases.rss` require releases
read access, and the tags page only shows tag names to users without it,
so the feed is the one place that leaks. A team member granted Code but
not Releases on a private repo, or anyone on a public repo whose
Releases unit is disabled, can read all release notes this way. I
noticed it while comparing the feed routes with the tags template. When
the doer cannot read releases, the feed now lists releases as plain
tags, linking to `/src/tag/` like the tags page does.
2026-10-22 20:25:44 +00:00

93 lines
3.0 KiB
Go

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"encoding/xml"
"net/http"
"testing"
auth_model "gitea.dev/models/auth"
repo_model "gitea.dev/models/repo"
unit_model "gitea.dev/models/unit"
"gitea.dev/models/unittest"
repo_service "gitea.dev/services/repository"
"gitea.dev/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFeedRepo(t *testing.T) {
t.Run("RSS", func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/user2/repo1.rss")
resp := MakeRequest(t, req, http.StatusOK)
data := resp.Body.String()
assert.Contains(t, data, `<rss version="2.0"`)
var rss RSS
err := xml.Unmarshal(resp.Body.Bytes(), &rss)
assert.NoError(t, err)
assert.Contains(t, rss.Channel.Link, "/user2/repo1")
assert.NotEmpty(t, rss.Channel.PubDate)
assert.Len(t, rss.Channel.Items, 1)
assert.Equal(t, "issue5", rss.Channel.Items[0].Description)
assert.NotEmpty(t, rss.Channel.Items[0].PubDate)
})
t.Run("TagsWithoutReleasesAccess", func(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
require.NoError(t, repo_service.UpdateRepositoryUnits(t.Context(), repo, nil, []unit_model.Type{unit_model.TypeReleases}))
resp := MakeRequest(t, NewRequest(t, "GET", "/user2/repo1/tags.rss"), http.StatusOK)
data := resp.Body.String()
assert.Contains(t, data, "/user2/repo1/src/tag/v1.0")
assert.NotContains(t, data, "pre-release")
assert.NotContains(t, data, "some text for a pre release")
})
}
// TestFeedRepoContentTokenScopes ensures repository feed endpoints enforce the
// repository token scope, so a PAT without repository scope cannot read private
// repository commit/activity data through RSS/Atom feeds.
func TestFeedRepoContentTokenScopes(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// user2/repo2 is a private repository owned by user2
ownerReadToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)
miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc)
urls := []string{
"/user2/repo2.rss",
"/user2/repo2.atom",
"/user2/repo2/rss/branch/master",
"/user2/repo2/atom/branch/master",
"/user2/repo2/rss/branch/master/README.md",
"/user2/repo2/tags.rss",
"/user2/repo2/tags.atom",
"/user2/repo2/releases.rss",
"/user2/repo2/releases.atom",
}
for _, url := range urls {
t.Run(url, func(t *testing.T) {
// feed routes only accept basic auth, so authenticate as the advisory PoC does (user:token)
reqDenied := NewRequest(t, "GET", url)
reqDenied.SetBasicAuth("user2", miscToken)
// a token without repository scope must be denied
MakeRequest(t, reqDenied, http.StatusForbidden)
reqAllowed := NewRequest(t, "GET", url)
reqAllowed.SetBasicAuth("user2", ownerReadToken)
// a token with repository read scope is allowed
MakeRequest(t, reqAllowed, http.StatusOK)
})
}
}