mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-02-22 11:25:47 -05:00
fix(sec): add tests for private issues on projects
- Add integration and unit tests to ensure that private issues on projects are not shown in any way, shape or form when the doer has no access to it.
This commit is contained in:
parent
b1b635c1d9
commit
51060d9826
6 changed files with 242 additions and 0 deletions
23
models/fixtures/PrivateIssueProjects/project.yml
Normal file
23
models/fixtures/PrivateIssueProjects/project.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
title: Org project that contains private issues
|
||||||
|
owner_id: 3
|
||||||
|
repo_id: 0
|
||||||
|
is_closed: false
|
||||||
|
creator_id: 2
|
||||||
|
board_type: 1
|
||||||
|
type: 3
|
||||||
|
created_unix: 1738000000
|
||||||
|
updated_unix: 1738000000
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1002
|
||||||
|
title: User project that contains private issues
|
||||||
|
owner_id: 2
|
||||||
|
repo_id: 0
|
||||||
|
is_closed: false
|
||||||
|
creator_id: 2
|
||||||
|
board_type: 1
|
||||||
|
type: 1
|
||||||
|
created_unix: 1738000000
|
||||||
|
updated_unix: 1738000000
|
17
models/fixtures/PrivateIssueProjects/project_board.yml
Normal file
17
models/fixtures/PrivateIssueProjects/project_board.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
project_id: 1001
|
||||||
|
title: Triage
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1738000000
|
||||||
|
updated_unix: 1738000000
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1002
|
||||||
|
project_id: 1002
|
||||||
|
title: Triage
|
||||||
|
creator_id: 2
|
||||||
|
default: true
|
||||||
|
created_unix: 1738000000
|
||||||
|
updated_unix: 1738000000
|
11
models/fixtures/PrivateIssueProjects/project_issue.yml
Normal file
11
models/fixtures/PrivateIssueProjects/project_issue.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
issue_id: 6
|
||||||
|
project_id: 1001
|
||||||
|
project_board_id: 1001
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1002
|
||||||
|
issue_id: 7
|
||||||
|
project_id: 1002
|
||||||
|
project_board_id: 1002
|
|
@ -1,42 +1,49 @@
|
||||||
-
|
-
|
||||||
id: 1
|
id: 1
|
||||||
team_id: 1
|
team_id: 1
|
||||||
|
org_id: 3
|
||||||
type: 1
|
type: 1
|
||||||
access_mode: 4
|
access_mode: 4
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 2
|
id: 2
|
||||||
team_id: 1
|
team_id: 1
|
||||||
|
org_id: 3
|
||||||
type: 2
|
type: 2
|
||||||
access_mode: 4
|
access_mode: 4
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 3
|
id: 3
|
||||||
team_id: 1
|
team_id: 1
|
||||||
|
org_id: 3
|
||||||
type: 3
|
type: 3
|
||||||
access_mode: 4
|
access_mode: 4
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 4
|
id: 4
|
||||||
team_id: 1
|
team_id: 1
|
||||||
|
org_id: 3
|
||||||
type: 4
|
type: 4
|
||||||
access_mode: 4
|
access_mode: 4
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 5
|
id: 5
|
||||||
team_id: 1
|
team_id: 1
|
||||||
|
org_id: 3
|
||||||
type: 5
|
type: 5
|
||||||
access_mode: 4
|
access_mode: 4
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 6
|
id: 6
|
||||||
team_id: 1
|
team_id: 1
|
||||||
|
org_id: 3
|
||||||
type: 6
|
type: 6
|
||||||
access_mode: 4
|
access_mode: 4
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 7
|
id: 7
|
||||||
team_id: 1
|
team_id: 1
|
||||||
|
org_id: 3
|
||||||
type: 7
|
type: 7
|
||||||
access_mode: 4
|
access_mode: 4
|
||||||
|
|
||||||
|
|
100
models/issues/issue_project_test.go
Normal file
100
models/issues/issue_project_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package issues_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrivateIssueProjects(t *testing.T) {
|
||||||
|
defer tests.AddFixtures("models/fixtures/PrivateIssueProjects/")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
t.Run("Organization project", func(t *testing.T) {
|
||||||
|
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||||
|
orgProject := unittest.AssertExistsAndLoadBean(t, &project.Project{ID: 1001, OwnerID: org.ID})
|
||||||
|
column := unittest.AssertExistsAndLoadBean(t, &project.Column{ID: 1001, ProjectID: orgProject.ID})
|
||||||
|
|
||||||
|
t.Run("Authenticated user", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, user2, org, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, issueList, 1)
|
||||||
|
assert.EqualValues(t, 6, issueList[0].ID)
|
||||||
|
|
||||||
|
issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, issuesNum)
|
||||||
|
|
||||||
|
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.Some(true))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, issuesNum)
|
||||||
|
|
||||||
|
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.Some(false))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, issuesNum)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Anonymous user", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, nil, org, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, issueList)
|
||||||
|
|
||||||
|
issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, orgProject, nil, org, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, issuesNum)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("User project", func(t *testing.T) {
|
||||||
|
userProject := unittest.AssertExistsAndLoadBean(t, &project.Project{ID: 1002, OwnerID: user2.ID})
|
||||||
|
column := unittest.AssertExistsAndLoadBean(t, &project.Column{ID: 1002, ProjectID: userProject.ID})
|
||||||
|
|
||||||
|
t.Run("Authenticated user", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, user2, nil, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, issueList, 1)
|
||||||
|
assert.EqualValues(t, 7, issueList[0].ID)
|
||||||
|
|
||||||
|
issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, userProject, user2, nil, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, issuesNum)
|
||||||
|
|
||||||
|
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, userProject, user2, nil, optional.Some(true))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, issuesNum)
|
||||||
|
|
||||||
|
issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, userProject, user2, nil, optional.Some(false))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, issuesNum)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Anonymous user", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, nil, nil, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, issueList)
|
||||||
|
|
||||||
|
issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, userProject, nil, nil, optional.None[bool]())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, issuesNum)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
84
tests/integration/private_project_test.go
Normal file
84
tests/integration/private_project_test.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrivateIssueProject(t *testing.T) {
|
||||||
|
defer tests.AddFixtures("models/fixtures/PrivateIssueProjects/")()
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
sess := loginUser(t, user2.Name)
|
||||||
|
|
||||||
|
test := func(t *testing.T, sess *TestSession, username string, projectID int64, hasAccess bool) {
|
||||||
|
t.Helper()
|
||||||
|
defer tests.PrintCurrentTest(t, 1)()
|
||||||
|
|
||||||
|
// Test that the projects overview page shows the correct open and close issues.
|
||||||
|
req := NewRequestf(t, "GET", "%s/-/projects", username)
|
||||||
|
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
openCloseStats := htmlDoc.Find(".milestone-toolbar .group").First().Text()
|
||||||
|
if hasAccess {
|
||||||
|
assert.Contains(t, openCloseStats, "1\u00a0Open")
|
||||||
|
} else {
|
||||||
|
assert.Contains(t, openCloseStats, "0\u00a0Open")
|
||||||
|
}
|
||||||
|
assert.Contains(t, openCloseStats, "0\u00a0Closed")
|
||||||
|
|
||||||
|
// Check that on the project itself the issue is not shown.
|
||||||
|
req = NewRequestf(t, "GET", "%s/-/projects/%d", username, projectID)
|
||||||
|
resp = sess.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||||
|
htmlDoc.AssertElement(t, ".project-column .issue-card", hasAccess)
|
||||||
|
|
||||||
|
// And that the issue count is correct.
|
||||||
|
issueCount := strings.TrimSpace(htmlDoc.Find(".project-column-issue-count").Text())
|
||||||
|
if hasAccess {
|
||||||
|
assert.EqualValues(t, "1", issueCount)
|
||||||
|
} else {
|
||||||
|
assert.EqualValues(t, "0", issueCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Organization project", func(t *testing.T) {
|
||||||
|
org := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 3})
|
||||||
|
orgProject := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1001, OwnerID: org.ID})
|
||||||
|
|
||||||
|
t.Run("Authenticated user", func(t *testing.T) {
|
||||||
|
test(t, sess, org.Name, orgProject.ID, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Anonymous user", func(t *testing.T) {
|
||||||
|
test(t, emptyTestSession(t), org.Name, orgProject.ID, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("User project", func(t *testing.T) {
|
||||||
|
userProject := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1002, OwnerID: user2.ID})
|
||||||
|
|
||||||
|
t.Run("Authenticated user", func(t *testing.T) {
|
||||||
|
test(t, sess, user2.Name, userProject.ID, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Anonymous user", func(t *testing.T) {
|
||||||
|
test(t, emptyTestSession(t), user2.Name, userProject.ID, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue