diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index a3b65db97d..3788a0117e 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -614,6 +614,21 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) { } } +func mustEnableRepoProjects(ctx *context.APIContext) { + if unit.TypeProjects.UnitGlobalDisabled() { + ctx.NotFound("EnableRepoProjects", nil) + return + } + + if ctx.Repo.Repository != nil { + projectsUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeProjects) + if !ctx.Repo.CanRead(unit.TypeProjects) || !projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) { + ctx.NotFound("MustEnableRepoProjects", nil) + return + } + } +} + func mustEnableIssues(ctx *context.APIContext) { if !ctx.Repo.CanRead(unit.TypeIssues) { if log.IsTrace() { @@ -996,7 +1011,7 @@ func Routes() *web.Router { m.Group("", func() { m.Get("", org.GetProjects) m.Get("/{id}", org.GetProject) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) + }) m.Group("", func() { m.Post("", bind(api.CreateProjectOption{}), org.CreateProject) @@ -1014,13 +1029,8 @@ func Routes() *web.Router { m.Post("/move", org.MoveIssues) }) }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.APIContext) { - if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID { - ctx.NotFound("NewProject", nil) - return - } - }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker) + }, reqSelfOrAdmin()) + }, individualPermsChecker) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), context.UserAssignmentAPI()) @@ -1030,7 +1040,7 @@ func Routes() *web.Router { m.Group("", func() { m.Get("", repo.GetProjects) m.Get("/{id}", repo.GetProject) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) + }) m.Group("", func() { m.Post("", bind(api.CreateProjectOption{}), repo.CreateProject) @@ -1048,15 +1058,9 @@ func Routes() *web.Router { m.Post("/move", repo.MoveIssues) }) }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.APIContext) { - if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID { - ctx.NotFound("NewProject", nil) - return - } - }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker) - - }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment()) + }, reqRepoWriter(unit.TypeProjects), mustNotBeArchived) + }, individualPermsChecker) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment(), reqRepoReader(unit.TypeProjects), mustEnableRepoProjects) // Organizations (requires orgs scope) m.Group("orgs/{org}/-", func() { @@ -1064,7 +1068,7 @@ func Routes() *web.Router { m.Group("", func() { m.Get("", org.GetProjects) m.Get("/{id}", org.GetProject) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) + }) m.Group("", func() { m.Post("", bind(api.CreateProjectOption{}), org.CreateProject) @@ -1082,15 +1086,9 @@ func Routes() *web.Router { m.Post("/move", org.MoveIssues) }) }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.APIContext) { - if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID { - ctx.NotFound("NewProject", nil) - return - } - }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker) - - }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), orgAssignment(true)) + }, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true)) + }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), orgAssignment(true), reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) // Organizations (requires orgs scope) m.Group("orgs/{org}/{reponame}", func() { @@ -1098,7 +1096,7 @@ func Routes() *web.Router { m.Group("", func() { m.Get("", repo.GetProjects) m.Get("/{id}", repo.GetProject) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) + }) m.Group("", func() { m.Post("", bind(api.CreateProjectOption{}), repo.CreateProject) @@ -1116,15 +1114,9 @@ func Routes() *web.Router { m.Post("/move", repo.MoveIssues) }) }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.APIContext) { - if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID { - ctx.NotFound("NewProject", nil) - return - } - }) - }, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker) - - }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment()) + }, reqRepoWriter(unit.TypeProjects), mustNotBeArchived) + }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment(), reqRepoReader(unit.TypeProjects), mustEnableRepoProjects) // Users (requires user scope) m.Group("/users", func() { diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index 42e4fe1e56..268842d43b 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/optional" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" ) @@ -458,3 +459,78 @@ func MoveIssues(ctx *context.APIContext) { ctx.JSON(http.StatusOK, map[string]string{"message": "issues moved successfully"}) } + +func getActionIssues(ctx *context.APIContext) issues_model.IssueList { + type updateIssuesForm struct { + Issues []int64 `json:"issues"` + } + + form := &updateIssuesForm{} + + if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil { + ctx.ServerError("DecodeMovedIssuesForm", err) + return nil + } + + if len(form.Issues) == 0 { + return nil + } + + issueIDs := form.Issues + issues, err := issues_model.GetIssuesByIDs(ctx, issueIDs) + if err != nil { + ctx.ServerError("GetIssuesByIDs", err) + return nil + } + // Check access rights for all issues + issueUnitEnabled := ctx.Repo.CanRead(unit.TypeIssues) + prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests) + for _, issue := range issues { + if issue.RepoID != ctx.Repo.Repository.ID { + ctx.NotFound("some issue's RepoID is incorrect", errors.New("some issue's RepoID is incorrect")) + return nil + } + if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled { + ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) + return nil + } + if err = issue.LoadAttributes(ctx); err != nil { + ctx.ServerError("LoadAttributes", err) + return nil + } + } + return issues +} + +// UpdateIssueProject change an issue's project +func UpdateIssueProject(ctx *context.APIContext) { + issues := getActionIssues(ctx) + if ctx.Written() { + return + } + + if err := issues.LoadProjects(ctx); err != nil { + ctx.ServerError("LoadProjects", err) + return + } + if _, err := issues.LoadRepositories(ctx); err != nil { + ctx.ServerError("LoadProjects", err) + return + } + + projectID := ctx.FormInt64("project_id") + for _, issue := range issues { + if issue.Project != nil && issue.Project.ID == projectID { + continue + } + if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil { + if errors.Is(err, util.ErrPermissionDenied) { + continue + } + ctx.ServerError("IssueAssignOrRemoveProject", err) + return + } + } + + ctx.JSON(http.StatusOK, map[string]string{"message": "issues moved successfully"}) +}