From 25fff4c043b8a978fc42b2259e738eff2e35d9a5 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sat, 22 Aug 2026 13:06:24 -0700 Subject: [PATCH] fix(markup): enforce same-repository issue access (#39045) (#39054) Backport #39045 by @bircni Enforce Issues and Pull Requests access for references within the current repository. Co-authored-by: bircni Co-authored-by: silverwind Co-authored-by: wxiaoguang --- .../markup/renderhelper_issueicontitle.go | 9 +-- .../renderhelper_issueicontitle_test.go | 65 ++++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/services/markup/renderhelper_issueicontitle.go b/services/markup/renderhelper_issueicontitle.go index 6f315951749..a62257a89bf 100644 --- a/services/markup/renderhelper_issueicontitle.go +++ b/services/markup/renderhelper_issueicontitle.go @@ -42,14 +42,15 @@ func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTi return "", err } + permission := webCtx.Repo.Permission if webCtx.Repo.Repository == nil || dbRepo.ID != webCtx.Repo.Repository.ID { - perms, err := access.GetDoerRepoPermission(ctx, dbRepo, webCtx.Doer) + permission, err = access.GetDoerRepoPermission(ctx, dbRepo, webCtx.Doer) if err != nil { return "", err } - if !perms.CanReadIssuesOrPulls(issue.IsPull) { - return "", util.ErrPermissionDenied - } + } + if !permission.CanReadIssuesOrPulls(issue.IsPull) { + return "", util.ErrPermissionDenied } if issue.IsPull { diff --git a/services/markup/renderhelper_issueicontitle_test.go b/services/markup/renderhelper_issueicontitle_test.go index 91b3f242e87..94cc975d2e6 100644 --- a/services/markup/renderhelper_issueicontitle_test.go +++ b/services/markup/renderhelper_issueicontitle_test.go @@ -6,7 +6,10 @@ package markup import ( "testing" + issue_model "gitea.dev/models/issues" + perm_model "gitea.dev/models/perm" "gitea.dev/models/repo" + "gitea.dev/models/unit" "gitea.dev/models/unittest" "gitea.dev/modules/markup" "gitea.dev/modules/templates" @@ -14,36 +17,52 @@ import ( "gitea.dev/services/contexttest" "github.com/stretchr/testify/assert" + "xorm.io/builder" ) func TestRenderHelperIssueIconTitle(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()}) - ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) - htm, err := renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ - LinkHref: "/link", - IssueIndex: 1, - }) - assert.NoError(t, err) - assert.Equal(t, `octicon-issue-opened(16/tw-text-green) issue1 (#1)`, string(htm)) + t.Run("RenderInCurrentRepo", func(t *testing.T) { + ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()}) + contexttest.LoadRepo(t, ctx, 1) + htm, err := renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ + LinkHref: "/link", + IssueIndex: 1, + }) + assert.NoError(t, err) + assert.Equal(t, `octicon-issue-opened(16/tw-text-green) issue1 (#1)`, string(htm)) - ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()}) - htm, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ - OwnerName: "user2", - RepoName: "repo1", - LinkHref: "/link", - IssueIndex: 1, + ctx.Repo.Permission.SetUnitsWithDefaultAccessMode([]*repo.RepoUnit{{Type: unit.TypeWiki}}, perm_model.AccessModeRead) + issueA := unittest.AssertExistsAndLoadBean(t, &issue_model.Issue{}, builder.Eq{"repo_id": 1, "`index`": 1, "is_pull": false}) + issueB := unittest.AssertExistsAndLoadBean(t, &issue_model.Issue{}, builder.Eq{"repo_id": 1, "`index`": 2, "is_pull": true}) + for _, issueIndex := range []int64{issueA.Index, issueB.Index} { + _, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ + LinkHref: "/link", + IssueIndex: issueIndex, + }) + assert.ErrorIs(t, err, util.ErrPermissionDenied) + } }) - assert.NoError(t, err) - assert.Equal(t, `octicon-issue-opened(16/tw-text-green) issue1 (user2/repo1#1)`, string(htm)) - ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()}) - _, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ - OwnerName: "user2", - RepoName: "repo2", - LinkHref: "/link", - IssueIndex: 2, + t.Run("RenderAcrossRepo", func(t *testing.T) { + ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()}) + htm, err := renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ + OwnerName: "user2", + RepoName: "repo1", + LinkHref: "/link", + IssueIndex: 1, + }) + assert.NoError(t, err) + assert.Equal(t, `octicon-issue-opened(16/tw-text-green) issue1 (user2/repo1#1)`, string(htm)) + + ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()}) + _, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{ + OwnerName: "user2", + RepoName: "repo2", + LinkHref: "/link", + IssueIndex: 2, + }) + assert.ErrorIs(t, err, util.ErrPermissionDenied) }) - assert.ErrorIs(t, err, util.ErrPermissionDenied) }