mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-07-19 07:58:38 +00:00
[v16.0/forgejo] fix: make assignment of projects to issues consistent (#13504)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/13066 A partial backport of the `tests/forgery` changes from `feat: make delete repository transcation READ COMMITTED (#12856)` is included and makes it a clean cherry-pick. - Move the permissions and consistency logic to services/context/project.go - When an invalid `project=id` is provided in the query string to display the form to create a new issue, it will now fail instead of silently discard the invalid id Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/13504 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
parent
1c2b530d08
commit
d3db2674b9
6 changed files with 516 additions and 125 deletions
|
|
@ -995,17 +995,10 @@ func NewIssue(ctx *context.Context) {
|
|||
}
|
||||
|
||||
projectID := ctx.FormInt64("project")
|
||||
if projectID > 0 && (isProjectsEnabled || isOwnerProjectsEnabled) {
|
||||
project, err := project_model.GetProjectByID(ctx, projectID)
|
||||
if err != nil {
|
||||
log.Error("GetProjectByID: %d: %v", projectID, err)
|
||||
} else if !project.CanBeAccessedByOwnerRepo(ctx.Repo.Repository.OwnerID, ctx.Repo.Repository) {
|
||||
log.Error("GetProjectByID: %d: %v", projectID,
|
||||
fmt.Errorf("project[%d] neither in repo[%d] nor has the same owner (project: [%d] ./. repo: [%d])",
|
||||
project.ID, ctx.Repo.Repository.ID, project.OwnerID, ctx.Repo.Repository.OwnerID))
|
||||
} else {
|
||||
ctx.Data["project_id"] = projectID
|
||||
ctx.Data["Project"] = project
|
||||
if projectID > 0 {
|
||||
context.ReqProjectIDAssignableToIssueAndSetData(ctx, projectID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ctx.Req.URL.Query().Get("project")) > 0 {
|
||||
|
|
@ -1168,18 +1161,10 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
|
|||
}
|
||||
|
||||
if form.ProjectID > 0 {
|
||||
p, err := project_model.GetProjectByID(ctx, form.ProjectID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetProjectByID", err)
|
||||
context.ReqProjectIDAssignableToIssueAndSetData(ctx, form.ProjectID)
|
||||
if ctx.Written() {
|
||||
return nil, nil, 0, 0
|
||||
}
|
||||
if p.RepoID != ctx.Repo.Repository.ID && p.OwnerID != ctx.Repo.Repository.OwnerID {
|
||||
ctx.NotFound("", nil)
|
||||
return nil, nil, 0, 0
|
||||
}
|
||||
|
||||
ctx.Data["Project"] = p
|
||||
ctx.Data["project_id"] = form.ProjectID
|
||||
}
|
||||
|
||||
// Check assignees
|
||||
|
|
@ -1219,6 +1204,17 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
|
|||
return labelIDs, assigneeIDs, milestoneID, form.ProjectID
|
||||
}
|
||||
|
||||
func updateIssueProject(ctx *context.Context, issue *issues_model.Issue, projectID int64) {
|
||||
context.ReqProjectIDAssignableToIssue(ctx, projectID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil {
|
||||
ctx.ServerError("IssueAssignOrRemoveProject", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NewIssuePost response for creating new issue
|
||||
func NewIssuePost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CreateIssueForm)
|
||||
|
|
@ -1288,13 +1284,8 @@ func NewIssuePost(ctx *context.Context) {
|
|||
}
|
||||
|
||||
if projectID > 0 {
|
||||
if !ctx.Repo.CanRead(unit.TypeProjects) || (ctx.ContextUser.IsOrganization() && !ctx.Org.CanReadUnit(ctx, unit.TypeProjects)) {
|
||||
// User must also be able to see the project.
|
||||
ctx.Error(http.StatusForbidden, "user doesn't have permissions to read projects")
|
||||
return
|
||||
}
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil {
|
||||
ctx.ServerError("IssueAssignOrRemoveProject", err)
|
||||
updateIssueProject(ctx, issue, projectID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1680,12 +1680,10 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if projectID > 0 && ctx.Repo.CanWrite(unit.TypeProjects) {
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, pullIssue, ctx.Doer, projectID, 0); err != nil {
|
||||
if !errors.Is(err, util.ErrPermissionDenied) {
|
||||
ctx.ServerError("IssueAssignOrRemoveProject", err)
|
||||
return
|
||||
}
|
||||
if projectID > 0 {
|
||||
updateIssueProject(ctx, pullIssue, projectID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1005,26 +1005,9 @@ func registerRoutes(m *web.Route) {
|
|||
}
|
||||
|
||||
reqRepoOrOwnerProjectReader := func(ctx *context.Context) {
|
||||
unitType := unit.TypeProjects
|
||||
if ctx.ContextUser == nil || ctx.Doer == nil {
|
||||
ctx.NotFound(unitType.String(), nil)
|
||||
return
|
||||
if projectID := ctx.FormInt64("id"); projectID > 0 {
|
||||
context.ReqProjectIDAssignableToIssue(ctx, projectID)
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.ContextUser.IsIndividual():
|
||||
if ctx.Doer.ID == ctx.ContextUser.ID || ctx.Doer.IsAdmin {
|
||||
return
|
||||
}
|
||||
case ctx.ContextUser.IsOrganization():
|
||||
if ctx.Org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeRead {
|
||||
return
|
||||
}
|
||||
default:
|
||||
ctx.NotFound(unitType.String(), nil)
|
||||
return
|
||||
}
|
||||
reqRepoProjectsReader(ctx)
|
||||
}
|
||||
|
||||
individualPermsChecker := func(ctx *context.Context) {
|
||||
|
|
|
|||
96
services/context/project.go
Normal file
96
services/context/project.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
project_model "forgejo.org/models/project"
|
||||
"forgejo.org/models/unit"
|
||||
"forgejo.org/modules/log"
|
||||
)
|
||||
|
||||
func ReqProjectIDAssignableToIssueAndSetData(ctx *Context, projectID int64) {
|
||||
project := getProjectID(ctx, projectID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
reqProjectAssignableToIssue(ctx, project)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.Data["Project"] = project
|
||||
ctx.Data["project_id"] = project.ID
|
||||
}
|
||||
|
||||
func ReqProjectIDAssignableToIssue(ctx *Context, projectID int64) {
|
||||
project := getProjectID(ctx, projectID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
reqProjectAssignableToIssue(ctx, project)
|
||||
}
|
||||
|
||||
func getProjectID(ctx *Context, projectID int64) *project_model.Project {
|
||||
project, err := project_model.GetProjectByID(ctx, projectID)
|
||||
if err != nil {
|
||||
if project_model.IsErrProjectNotExist(err) {
|
||||
ctx.NotFound(fmt.Sprintf("project %d is not found", projectID), nil)
|
||||
return nil
|
||||
}
|
||||
log.Error("project_model.GetProjectByID(%d): %v", projectID, err)
|
||||
ctx.ServerError(fmt.Sprintf("project_model.GetProjectByID(%d)", projectID), err)
|
||||
return nil
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
||||
func reqProjectAssignableToIssue(ctx *Context, project *project_model.Project) {
|
||||
reqValidAndConsistentProject(ctx, project)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
reqPermissionToAssignProjectToIssue(ctx, project.Type)
|
||||
}
|
||||
|
||||
func reqValidAndConsistentProject(ctx *Context, project *project_model.Project) {
|
||||
if project.RepoID != ctx.Repo.Repository.ID && project.OwnerID != ctx.Repo.Repository.OwnerID {
|
||||
ctx.NotFound(fmt.Sprintf("project %d does not belong", project.ID), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func reqPermissionToAssignProjectToIssue(ctx *Context, projectType project_model.Type) {
|
||||
switch projectType {
|
||||
case project_model.TypeRepository:
|
||||
if !ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeProjects) {
|
||||
ctx.NotFound("repository projects are disabled", nil)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeProjects) {
|
||||
ctx.Error(http.StatusForbidden, "doesn't have permissions to read repository projects")
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanWrite(unit.TypeIssues) {
|
||||
ctx.Error(http.StatusForbidden, "doesn't have permissions to write repository issues")
|
||||
return
|
||||
}
|
||||
case project_model.TypeOrganization:
|
||||
if !ctx.Org.CanReadUnit(ctx, unit.TypeProjects) {
|
||||
ctx.Error(http.StatusForbidden, "doesn't have permissions to read the owner projects")
|
||||
return
|
||||
}
|
||||
if !ctx.Org.CanWriteUnit(ctx, unit.TypeIssues) {
|
||||
ctx.Error(http.StatusForbidden, "doesn't have permissions to write the repository issues and set the project")
|
||||
return
|
||||
}
|
||||
case project_model.TypeIndividual:
|
||||
if !ctx.Repo.CanWrite(unit.TypeIssues) {
|
||||
ctx.Error(http.StatusForbidden, "doesn't have permissions to write the repository issues and set the project")
|
||||
return
|
||||
}
|
||||
default:
|
||||
ctx.ServerError(fmt.Sprintf("unexpected project type %v", projectType), nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,13 @@ import (
|
|||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/perm"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
unit_model "forgejo.org/models/unit"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/git"
|
||||
repo_module "forgejo.org/modules/repository"
|
||||
repo_service "forgejo.org/services/repository"
|
||||
wiki_service "forgejo.org/services/wiki"
|
||||
|
||||
|
|
@ -33,6 +36,8 @@ type CreateRepositoryOptions struct {
|
|||
|
||||
LatestSha *string // if not nil, the commit sha after initializing the repo with the Files will be written to this ref
|
||||
SkipCleanup bool // if true the repo will not be deleted at the end of the test (can be useful to debug locally)
|
||||
|
||||
Collaborators map[*user_model.User]perm.AccessMode
|
||||
}
|
||||
|
||||
// FilesInit specifies the templates to use upon repository initialization.
|
||||
|
|
@ -88,7 +93,7 @@ func CreateRepository(t testing.TB, owner *user_model.User, opts *CreateReposito
|
|||
require.NoError(t, err)
|
||||
if !opts.SkipCleanup {
|
||||
t.Cleanup(func() {
|
||||
_ = repo_service.DeleteRepository(t.Context(), owner, repo, false)
|
||||
_ = repo_service.DeleteRepository(db.DefaultContext, owner, repo, false)
|
||||
})
|
||||
}
|
||||
require.NotEmpty(t, repo)
|
||||
|
|
@ -106,6 +111,11 @@ func CreateRepository(t testing.TB, owner *user_model.User, opts *CreateReposito
|
|||
}
|
||||
repo.Owner = owner
|
||||
|
||||
for user, mode := range opts.Collaborators {
|
||||
require.NoError(t, repo_module.AddCollaborator(t.Context(), repo, user))
|
||||
require.NoError(t, repo_model.ChangeCollaborationAccessMode(t.Context(), repo, user.ID, mode))
|
||||
}
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package integration
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -14,14 +15,15 @@ import (
|
|||
|
||||
"forgejo.org/models/db"
|
||||
issues_model "forgejo.org/models/issues"
|
||||
org_model "forgejo.org/models/organization"
|
||||
"forgejo.org/models/perm"
|
||||
project_model "forgejo.org/models/project"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
unit_model "forgejo.org/models/unit"
|
||||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
repo_service "forgejo.org/services/repository"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/tests"
|
||||
"forgejo.org/tests/forgery"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -169,16 +171,40 @@ func TestChangeStatusProject(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAssignProject(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("tests/integration/fixtures/TestAssignProject/")()
|
||||
func TestProjectPermissionsAndConsistency(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
newTestIssue := func(t *testing.T, session *TestSession, owner *user_model.User, repo *repo_model.Repository) (*issues_model.Issue, string, string) {
|
||||
newTestIssue := func(t *testing.T, session *TestSession, repo *repo_model.Repository, project *project_model.Project, expectedStatus int) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
|
||||
issueURL := testNewIssue(t, session, owner.Name, repo.Name, "Hello", "World")
|
||||
req := NewRequest(t, "GET", path.Join(repo.FullName(), "issues", "new"))
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
link, exists := htmlDoc.doc.Find("#new-issue").Attr("action")
|
||||
require.True(t, exists, "The template has changed")
|
||||
|
||||
payload := map[string]string{
|
||||
"title": "Hello",
|
||||
"content": "World",
|
||||
}
|
||||
if project != nil {
|
||||
payload["project_id"] = strconv.FormatInt(project.ID, 10)
|
||||
}
|
||||
|
||||
req = NewRequestWithValues(t, "POST", link, payload)
|
||||
return session.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
newTestIssueSuccess := func(t *testing.T, session *TestSession, repo *repo_model.Repository, project *project_model.Project) *issues_model.Issue {
|
||||
t.Helper()
|
||||
|
||||
resp := newTestIssue(t, session, repo, project, http.StatusOK)
|
||||
|
||||
issueURL := test.RedirectURL(resp)
|
||||
|
||||
indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:]
|
||||
index, err := strconv.Atoi(indexStr)
|
||||
require.NoError(t, err, "Invalid issue href: %s", issueURL)
|
||||
|
|
@ -186,99 +212,386 @@ func TestAssignProject(t *testing.T) {
|
|||
issue := &issues_model.Issue{RepoID: repo.ID, Index: int64(index)}
|
||||
unittest.AssertExistsAndLoadBean(t, issue)
|
||||
|
||||
issueID := strconv.FormatInt(issue.ID, 10)
|
||||
return issue, indexStr, issueID
|
||||
if project != nil {
|
||||
require.NoError(t, issue.LoadProject(ctx))
|
||||
require.NotNil(t, issue.Project)
|
||||
require.Equal(t, project.ID, issue.Project.ID)
|
||||
}
|
||||
|
||||
return issue
|
||||
}
|
||||
|
||||
updateIssueProject := func(t *testing.T, session *TestSession, projectID, issueID, owner, repo string, expectedStatus int) {
|
||||
updateIssueProject := func(t *testing.T, session *TestSession, repo *repo_model.Repository, project *project_model.Project, issue *issues_model.Issue, expectedStatus int) {
|
||||
t.Helper()
|
||||
|
||||
req := NewRequestWithValues(t, "POST", path.Join(owner, repo, "issues", "projects"), map[string]string{
|
||||
"issue_ids": issueID,
|
||||
"id": projectID,
|
||||
req := NewRequestWithValues(t, "POST", path.Join(repo.FullName(), "issues", "projects"), map[string]string{
|
||||
"issue_ids": strconv.FormatInt(issue.ID, 10),
|
||||
"id": strconv.FormatInt(project.ID, 10),
|
||||
})
|
||||
session.MakeRequest(t, req, expectedStatus)
|
||||
|
||||
if expectedStatus == http.StatusOK {
|
||||
issue := &issues_model.Issue{ID: issue.ID}
|
||||
unittest.AssertExistsAndLoadBean(t, issue)
|
||||
issue.LoadProject(ctx)
|
||||
require.Equal(t, project.ID, issue.Project.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// User
|
||||
t.Run("UserProjectOn+RepoProjectOff", func(tt *testing.T) {
|
||||
defer tests.PrintCurrentTest(tt)()
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
clearIssueProject := func(t *testing.T, session *TestSession, repo *repo_model.Repository, issue *issues_model.Issue) {
|
||||
t.Helper()
|
||||
|
||||
session := loginUser(tt, user.Name)
|
||||
issue, _, issueID := newTestIssue(tt, session, user, repo)
|
||||
req := NewRequestWithValues(t, "POST", path.Join(repo.FullName(), "issues", "projects"), map[string]string{
|
||||
"issue_ids": strconv.FormatInt(issue.ID, 10),
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
updateIssueProject(tt, session, "1003", issueID, user.Name, repo.Name, http.StatusOK)
|
||||
require.NoError(tt, issue.LoadProject(db.DefaultContext))
|
||||
require.NotNil(tt, issue.Project)
|
||||
require.Equal(tt, int64(1003), issue.Project.ID)
|
||||
t.Run("New issue with project ID in query string", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
unittest.LoadFixtures()
|
||||
|
||||
getNewIssue := func(t *testing.T, session *TestSession, repo *repo_model.Repository, projectID int64, expectedStatus int) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s?project=%d", path.Join(repo.FullName(), "issues", "new"), projectID))
|
||||
return session.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
t.Run("does not exist anywhere", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
invalidProjectID := int64(4234243)
|
||||
session := loginUser(t, doer.Name)
|
||||
resp := getNewIssue(t, session, repo, invalidProjectID, http.StatusNotFound)
|
||||
assert.Contains(t, resp.Body.String(), "Not found.")
|
||||
})
|
||||
|
||||
t.Run("is a valid repository project", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
project := forgery.CreateProject(t, repo, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
getNewIssue(t, session, repo, project.ID, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("is invalid because it is a repository project that belongs to a different repository", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
otherRepo := forgery.CreateRepository(t, owner, nil)
|
||||
projectFromOtherRepo := forgery.CreateProject(t, otherRepo, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
resp := getNewIssue(t, session, repo, projectFromOtherRepo.ID, http.StatusNotFound)
|
||||
assert.Contains(t, resp.Body.String(), "Not found.")
|
||||
})
|
||||
|
||||
t.Run("is a valid owner project", func(t *testing.T) {
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, nil)
|
||||
doer := user
|
||||
|
||||
project := forgery.CreateProject(t, user, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
getNewIssue(t, session, repo, project.ID, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("is invalid because it is an owner project that belongs to a different owner", func(t *testing.T) {
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, nil)
|
||||
doer := user
|
||||
|
||||
otherUser := forgery.CreateUser(t, nil)
|
||||
projectFromOtherUser := forgery.CreateProject(t, otherUser, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
resp := getNewIssue(t, session, repo, projectFromOtherUser.ID, http.StatusNotFound)
|
||||
assert.Contains(t, resp.Body.String(), "Not found.")
|
||||
})
|
||||
})
|
||||
|
||||
// Team 1001 - enabled project unit
|
||||
team := unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: 1001})
|
||||
require.NoError(t, team.LoadMembers(ctx))
|
||||
require.NoError(t, team.LoadRepositories(ctx))
|
||||
t.Run("Project ID", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
unittest.LoadFixtures()
|
||||
|
||||
user := team.Members[0]
|
||||
repo := team.Repos[0]
|
||||
org := team.GetOrg(ctx)
|
||||
t.Run("does not exist anywhere", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
session := loginUser(t, user.Name)
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
invalidProject := &project_model.Project{ID: 4234243}
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, invalidProject, http.StatusNotFound)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
t.Run("OrgProjectOn+RepoProjectOn", func(tt *testing.T) {
|
||||
defer tests.PrintCurrentTest(tt)()
|
||||
issue, _, issueID := newTestIssue(tt, session, org.AsUser(), repo)
|
||||
updateIssueProject(t, session, repo, invalidProject, issue, http.StatusNotFound)
|
||||
})
|
||||
|
||||
updateIssueProject(tt, session, "1001", issueID, org.Name, repo.Name, http.StatusOK)
|
||||
t.Run("is a valid repository project", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
require.NoError(tt, issue.LoadProject(db.DefaultContext))
|
||||
require.NotNil(tt, issue.Project)
|
||||
require.Equal(tt, int64(1001), issue.Project.ID)
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
projectA := forgery.CreateProject(t, repo, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
projectB := forgery.CreateProject(t, repo, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
t.Run("is invalid because it is a repository project that belongs to a different repository", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
otherRepo := forgery.CreateRepository(t, owner, nil)
|
||||
projectFromOtherRepo := forgery.CreateProject(t, otherRepo, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, projectFromOtherRepo, http.StatusNotFound)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, projectFromOtherRepo, issue, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("is a valid owner project", func(t *testing.T) {
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, nil)
|
||||
doer := user
|
||||
|
||||
projectA := forgery.CreateProject(t, user, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
projectB := forgery.CreateProject(t, user, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
t.Run("is invalid because it is an owner project that belongs to a different owner", func(t *testing.T) {
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, nil)
|
||||
doer := user
|
||||
|
||||
otherUser := forgery.CreateUser(t, nil)
|
||||
projectFromOtherUser := forgery.CreateProject(t, otherUser, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, projectFromOtherUser, http.StatusNotFound)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, projectFromOtherUser, issue, http.StatusNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
// Disable repository project unit
|
||||
require.NoError(t, repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit_model.Type{unit_model.TypeProjects}))
|
||||
t.Run("OrgProjectOn+RepoProjectOff", func(tt *testing.T) {
|
||||
defer tests.PrintCurrentTest(tt)()
|
||||
issue, _, issueID := newTestIssue(tt, session, org.AsUser(), repo)
|
||||
t.Run("Repository project", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
unittest.LoadFixtures()
|
||||
|
||||
updateIssueProject(tt, session, "1001", issueID, org.Name, repo.Name, http.StatusOK)
|
||||
require.NoError(tt, issue.LoadProject(db.DefaultContext))
|
||||
require.NotNil(tt, issue.Project)
|
||||
require.Equal(tt, int64(1001), issue.Project.ID)
|
||||
t.Run("doer is owner", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
projectA := forgery.CreateProject(t, repo, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
projectB := forgery.CreateProject(t, repo, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
t.Run("doer is owner but the projects unit is disabled", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
|
||||
repo := forgery.CreateRepository(t, owner, nil)
|
||||
project := forgery.CreateProject(t, repo, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, project, http.StatusNotFound)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, project, issue, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("doer is collaborator with write permissions", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, &forgery.CreateRepositoryOptions{
|
||||
Collaborators: map[*user_model.User]perm.AccessMode{doer: perm.AccessModeWrite},
|
||||
})
|
||||
|
||||
projectA := forgery.CreateProject(t, repo, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
projectB := forgery.CreateProject(t, repo, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
t.Run("doer is collaborator with read permissions", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, &forgery.CreateRepositoryOptions{
|
||||
Collaborators: map[*user_model.User]perm.AccessMode{doer: perm.AccessModeRead},
|
||||
})
|
||||
|
||||
project := forgery.CreateProject(t, repo, nil)
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, project, http.StatusForbidden)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, project, issue, http.StatusNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
// Team 1002 - disabled project unit
|
||||
team = unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: 1002})
|
||||
require.NoError(t, team.LoadMembers(ctx))
|
||||
require.NoError(t, team.LoadRepositories(ctx))
|
||||
t.Run("Organization project", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
unittest.LoadFixtures()
|
||||
|
||||
user = team.Members[0]
|
||||
repo = team.Repos[0]
|
||||
org = team.GetOrg(ctx)
|
||||
t.Run("doer is the organization owner", func(t *testing.T) {
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
doer := owner
|
||||
org := forgery.CreateOrganisation(t, owner)
|
||||
|
||||
session = loginUser(t, user.Name)
|
||||
repo := forgery.CreateRepository(t, org.AsUser(), nil)
|
||||
projectA := forgery.CreateProject(t, org, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
t.Run("OrgProjectOff+RepoProjectOn", func(tt *testing.T) {
|
||||
defer tests.PrintCurrentTest(tt)()
|
||||
issue, _, issueID := newTestIssue(tt, session, org.AsUser(), repo)
|
||||
projectB := forgery.CreateProject(t, org, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
updateIssueProject(tt, session, "1002", issueID, org.Name, repo.Name, http.StatusOK)
|
||||
require.NoError(tt, issue.LoadProject(db.DefaultContext))
|
||||
require.NotNil(tt, issue.Project)
|
||||
require.Equal(tt, int64(1002), issue.Project.ID)
|
||||
t.Run("doer in team with write permissions", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
owner := forgery.CreateUser(t, nil)
|
||||
org := forgery.CreateOrganisation(t, owner)
|
||||
forgery.CreateTeam(t, org, &forgery.CreateTeamOptions{
|
||||
Mode: perm.AccessModeWrite,
|
||||
Members: []*user_model.User{doer},
|
||||
})
|
||||
|
||||
repo := forgery.CreateRepository(t, org.AsUser(), nil)
|
||||
projectA := forgery.CreateProject(t, org, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
projectB := forgery.CreateProject(t, org, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
t.Run("doer in a team with read permissions", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
org := forgery.CreateOrganisation(t, nil)
|
||||
forgery.CreateTeam(t, org, &forgery.CreateTeamOptions{
|
||||
Mode: perm.AccessModeRead,
|
||||
Members: []*user_model.User{doer},
|
||||
})
|
||||
|
||||
repo := forgery.CreateRepository(t, org.AsUser(), nil)
|
||||
project := forgery.CreateProject(t, org, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, project, http.StatusForbidden)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, project, issue, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("doer not in any team", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
org := forgery.CreateOrganisation(t, nil)
|
||||
|
||||
repo := forgery.CreateRepository(t, org.AsUser(), nil)
|
||||
project := forgery.CreateProject(t, org, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, project, http.StatusForbidden)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, project, issue, http.StatusNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
// Disable repository project unit
|
||||
require.NoError(t, repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit_model.Type{unit_model.TypeProjects}))
|
||||
t.Run("OrgProjectOff+RepoProjectOff", func(tt *testing.T) {
|
||||
defer tests.PrintCurrentTest(tt)()
|
||||
issue, _, issueID := newTestIssue(tt, session, org.AsUser(), repo)
|
||||
t.Run("User project", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
unittest.LoadFixtures()
|
||||
|
||||
updateIssueProject(tt, session, "1002", issueID, org.Name, repo.Name, http.StatusOK)
|
||||
require.NoError(tt, issue.LoadProject(db.DefaultContext))
|
||||
require.NotNil(tt, issue.Project)
|
||||
require.Equal(tt, int64(1002), issue.Project.ID)
|
||||
t.Run("doer is owner", func(t *testing.T) {
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, nil)
|
||||
doer := user
|
||||
|
||||
projectA := forgery.CreateProject(t, user, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
projectB := forgery.CreateProject(t, user, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
t.Run("doer is collaborator with write permissions", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, &forgery.CreateRepositoryOptions{
|
||||
Collaborators: map[*user_model.User]perm.AccessMode{doer: perm.AccessModeWrite},
|
||||
})
|
||||
|
||||
projectA := forgery.CreateProject(t, user, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
issue := newTestIssueSuccess(t, session, repo, projectA)
|
||||
|
||||
projectB := forgery.CreateProject(t, user, nil)
|
||||
updateIssueProject(t, session, repo, projectB, issue, http.StatusOK)
|
||||
clearIssueProject(t, session, repo, issue)
|
||||
})
|
||||
|
||||
t.Run("doer is collaborator with read permissions", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, &forgery.CreateRepositoryOptions{
|
||||
Collaborators: map[*user_model.User]perm.AccessMode{doer: perm.AccessModeRead},
|
||||
})
|
||||
|
||||
project := forgery.CreateProject(t, user, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, project, http.StatusForbidden)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, project, issue, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("doer is not a collaborator or owner", func(t *testing.T) {
|
||||
doer := forgery.CreateUser(t, nil)
|
||||
user := forgery.CreateUser(t, nil)
|
||||
repo := forgery.CreateRepository(t, user, nil)
|
||||
|
||||
project := forgery.CreateProject(t, user, nil)
|
||||
forgery.DisableRepoUnits(t, repo, unit_model.TypeProjects)
|
||||
session := loginUser(t, doer.Name)
|
||||
newTestIssue(t, session, repo, project, http.StatusForbidden)
|
||||
issue := newTestIssueSuccess(t, session, repo, nil)
|
||||
|
||||
updateIssueProject(t, session, repo, project, issue, http.StatusNotFound)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue