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
 | 
			
		||||
  team_id: 1
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  type: 1
 | 
			
		||||
  access_mode: 4
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 2
 | 
			
		||||
  team_id: 1
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  type: 2
 | 
			
		||||
  access_mode: 4
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 3
 | 
			
		||||
  team_id: 1
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  type: 3
 | 
			
		||||
  access_mode: 4
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 4
 | 
			
		||||
  team_id: 1
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  type: 4
 | 
			
		||||
  access_mode: 4
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 5
 | 
			
		||||
  team_id: 1
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  type: 5
 | 
			
		||||
  access_mode: 4
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 6
 | 
			
		||||
  team_id: 1
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  type: 6
 | 
			
		||||
  access_mode: 4
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 7
 | 
			
		||||
  team_id: 1
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  type: 7
 | 
			
		||||
  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
		Add a link
		
	
		Reference in a new issue