aboutsummaryrefslogtreecommitdiffstats
path: root/routers/api/v1/api.go
diff options
context:
space:
mode:
Diffstat (limited to 'routers/api/v1/api.go')
-rw-r--r--routers/api/v1/api.go344
1 files changed, 230 insertions, 114 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index b1a42a85e6..4a4bf12657 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -7,8 +7,6 @@
// This documentation describes the Gitea API.
//
// Schemes: https, http
-// BasePath: /api/v1
-// Version: {{AppVer | JSEscape}}
// License: MIT http://opensource.org/licenses/MIT
//
// Consumes:
@@ -66,6 +64,8 @@
package v1
import (
+ gocontext "context"
+ "errors"
"fmt"
"net/http"
"strings"
@@ -116,9 +116,9 @@ func sudo() func(ctx *context.APIContext) {
user, err := user_model.GetUserByName(ctx, sudo)
if err != nil {
if user_model.IsErrUserNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -154,12 +154,12 @@ func repoAssignment() func(ctx *context.APIContext) {
if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
context.RedirectToUser(ctx.Base, userName, redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
- ctx.NotFound("GetUserByName", err)
+ ctx.APIErrorNotFound("GetUserByName", err)
} else {
- ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
+ ctx.APIErrorInternal(err)
}
} else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -175,12 +175,12 @@ func repoAssignment() func(ctx *context.APIContext) {
if err == nil {
context.RedirectToRepo(ctx.Base, redirectRepoID)
} else if repo_model.IsErrRedirectNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err)
+ ctx.APIErrorInternal(err)
}
} else {
- ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -192,11 +192,11 @@ func repoAssignment() func(ctx *context.APIContext) {
taskID := ctx.Data["ActionsTaskID"].(int64)
task, err := actions_model.GetTaskByID(ctx, taskID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err)
+ ctx.APIErrorInternal(err)
return
}
if task.RepoID != repo.ID {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
@@ -207,29 +207,52 @@ func repoAssignment() func(ctx *context.APIContext) {
}
if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
+ ctx.APIErrorInternal(err)
return
}
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
} else {
- ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ needTwoFactor, err := doerNeedTwoFactorAuth(ctx, ctx.Doer)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
+ ctx.APIErrorInternal(err)
return
}
+ if needTwoFactor {
+ ctx.Repo.Permission = access_model.PermissionNoAccess()
+ } else {
+ ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
}
- if !ctx.Repo.Permission.HasAnyUnitAccess() {
- ctx.NotFound()
+ if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() {
+ ctx.APIErrorNotFound()
return
}
}
}
+func doerNeedTwoFactorAuth(ctx gocontext.Context, doer *user_model.User) (bool, error) {
+ if !setting.TwoFactorAuthEnforced {
+ return false, nil
+ }
+ if doer == nil {
+ return false, nil
+ }
+ has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, doer.ID)
+ if err != nil {
+ return false, err
+ }
+ return !has, nil
+}
+
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqPackageAccess", "user should have specific permission or be a site admin")
+ ctx.APIError(http.StatusForbidden, "user should have specific permission or be a site admin")
return
}
}
@@ -250,41 +273,41 @@ func checkTokenPublicOnly() func(ctx *context.APIContext) {
switch {
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public repos")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public issues")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
return
}
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
- if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
+ if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public users")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
- if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
+ if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public activitypub")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public notifications")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
+ ctx.APIError(http.StatusForbidden, "token scope is limited to public packages")
return
}
}
@@ -308,7 +331,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
// use the http method to determine the access level
requiredScopeLevel := auth_model.Read
- if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
+ if ctx.Req.Method == http.MethodPost || ctx.Req.Method == http.MethodPut || ctx.Req.Method == http.MethodPatch || ctx.Req.Method == http.MethodDelete {
requiredScopeLevel = auth_model.Write
}
@@ -316,12 +339,12 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
allow, err := scope.HasScope(requiredScopes...)
if err != nil {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
+ ctx.APIError(http.StatusForbidden, "checking scope failed: "+err.Error())
return
}
if !allow {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s), required=%v, token scope=%v", requiredScopes, scope))
+ ctx.APIError(http.StatusForbidden, fmt.Sprintf("token does not have at least one of required scope(s), required=%v, token scope=%v", requiredScopes, scope))
return
}
@@ -330,7 +353,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
// check if scope only applies to public resources
publicOnly, err := scope.PublicOnly()
if err != nil {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
+ ctx.APIError(http.StatusForbidden, "parsing public resource scope failed: "+err.Error())
return
}
@@ -350,14 +373,14 @@ func reqToken() func(ctx *context.APIContext) {
if ctx.IsSigned {
return
}
- ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
+ ctx.APIError(http.StatusUnauthorized, "token is required")
}
}
func reqExploreSignIn() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
- if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
- ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
+ if (setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
+ ctx.APIError(http.StatusUnauthorized, "you must be signed in to search for users")
}
}
}
@@ -365,7 +388,7 @@ func reqExploreSignIn() func(ctx *context.APIContext) {
func reqUsersExploreEnabled() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if setting.Service.Explore.DisableUsersPage {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
}
}
@@ -376,7 +399,7 @@ func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
return
}
if !ctx.IsBasicAuth {
- ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
+ ctx.APIError(http.StatusUnauthorized, "auth required")
return
}
}
@@ -386,7 +409,7 @@ func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
func reqSiteAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
+ ctx.APIError(http.StatusForbidden, "user should be the site admin")
return
}
}
@@ -396,7 +419,7 @@ func reqSiteAdmin() func(ctx *context.APIContext) {
func reqOwner() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Repo.IsOwner() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
+ ctx.APIError(http.StatusForbidden, "user should be the owner of the repo")
return
}
}
@@ -406,7 +429,7 @@ func reqOwner() func(ctx *context.APIContext) {
func reqSelfOrAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() && ctx.ContextUser != ctx.Doer {
- ctx.Error(http.StatusForbidden, "reqSelfOrAdmin", "doer should be the site admin or be same as the contextUser")
+ ctx.APIError(http.StatusForbidden, "doer should be the site admin or be same as the contextUser")
return
}
}
@@ -416,7 +439,7 @@ func reqSelfOrAdmin() func(ctx *context.APIContext) {
func reqAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
+ ctx.APIError(http.StatusForbidden, "user should be an owner or a collaborator with admin write of a repository")
return
}
}
@@ -426,26 +449,17 @@ func reqAdmin() func(ctx *context.APIContext) {
func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
+ ctx.APIError(http.StatusForbidden, "user should have a permission to write to a repo")
return
}
}
}
-// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
-func reqRepoBranchWriter(ctx *context.APIContext) {
- options, ok := web.GetForm(ctx).(api.FileOptionInterface)
- if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
- ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch")
- return
- }
-}
-
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
+ ctx.APIError(http.StatusForbidden, "user should have specific read permission or be a repo admin or a site admin")
return
}
}
@@ -455,7 +469,7 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
func reqAnyRepoReader() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Repo.Permission.HasAnyUnitAccess() && !ctx.IsUserSiteAdmin() {
- ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
+ ctx.APIError(http.StatusForbidden, "user should have any permission to read repository or permissions of site admin")
return
}
}
@@ -474,19 +488,20 @@ func reqOrgOwnership() func(ctx *context.APIContext) {
} else if ctx.Org.Team != nil {
orgID = ctx.Org.Team.OrgID
} else {
- ctx.Error(http.StatusInternalServerError, "", "reqOrgOwnership: unprepared context")
+ setting.PanicInDevOrTesting("reqOrgOwnership: unprepared context")
+ ctx.APIErrorInternal(errors.New("reqOrgOwnership: unprepared context"))
return
}
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
+ ctx.APIErrorInternal(err)
return
} else if !isOwner {
if ctx.Org.Organization != nil {
- ctx.Error(http.StatusForbidden, "", "Must be an organization owner")
+ ctx.APIError(http.StatusForbidden, "Must be an organization owner")
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
return
}
@@ -500,30 +515,31 @@ func reqTeamMembership() func(ctx *context.APIContext) {
return
}
if ctx.Org.Team == nil {
- ctx.Error(http.StatusInternalServerError, "", "reqTeamMembership: unprepared context")
+ setting.PanicInDevOrTesting("reqTeamMembership: unprepared context")
+ ctx.APIErrorInternal(errors.New("reqTeamMembership: unprepared context"))
return
}
orgID := ctx.Org.Team.OrgID
isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
+ ctx.APIErrorInternal(err)
return
} else if isOwner {
return
}
if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "IsTeamMember", err)
+ ctx.APIErrorInternal(err)
return
} else if !isTeamMember {
isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
+ ctx.APIErrorInternal(err)
} else if isOrgMember {
- ctx.Error(http.StatusForbidden, "", "Must be a team member")
+ ctx.APIError(http.StatusForbidden, "Must be a team member")
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
return
}
@@ -543,18 +559,19 @@ func reqOrgMembership() func(ctx *context.APIContext) {
} else if ctx.Org.Team != nil {
orgID = ctx.Org.Team.OrgID
} else {
- ctx.Error(http.StatusInternalServerError, "", "reqOrgMembership: unprepared context")
+ setting.PanicInDevOrTesting("reqOrgMembership: unprepared context")
+ ctx.APIErrorInternal(errors.New("reqOrgMembership: unprepared context"))
return
}
if isMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID); err != nil {
- ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
+ ctx.APIErrorInternal(err)
return
} else if !isMember {
if ctx.Org.Organization != nil {
- ctx.Error(http.StatusForbidden, "", "Must be an organization member")
+ ctx.APIError(http.StatusForbidden, "Must be an organization member")
} else {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
}
return
}
@@ -564,7 +581,7 @@ func reqOrgMembership() func(ctx *context.APIContext) {
func reqGitHook() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Doer.CanEditGitHook() {
- ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
+ ctx.APIError(http.StatusForbidden, "must be allowed to edit Git hooks")
return
}
}
@@ -574,7 +591,17 @@ func reqGitHook() func(ctx *context.APIContext) {
func reqWebhooksEnabled() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if setting.DisableWebhooks {
- ctx.Error(http.StatusForbidden, "", "webhooks disabled by administrator")
+ ctx.APIError(http.StatusForbidden, "webhooks disabled by administrator")
+ return
+ }
+ }
+}
+
+// reqStarsEnabled requires Starring to be enabled in the config.
+func reqStarsEnabled() func(ctx *context.APIContext) {
+ return func(ctx *context.APIContext) {
+ if setting.Repository.DisableStars {
+ ctx.APIError(http.StatusForbidden, "stars disabled by administrator")
return
}
}
@@ -603,12 +630,12 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
if err == nil {
context.RedirectToUser(ctx.Base, ctx.PathParam("org"), redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
- ctx.NotFound("GetOrgByName", err)
+ ctx.APIErrorNotFound("GetOrgByName", err)
} else {
- ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
+ ctx.APIErrorInternal(err)
}
} else {
- ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -619,9 +646,9 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.PathParamInt64("teamid"))
if err != nil {
if organization.IsErrTeamNotExist(err) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
} else {
- ctx.Error(http.StatusInternalServerError, "GetTeamById", err)
+ ctx.APIErrorInternal(err)
}
return
}
@@ -647,7 +674,7 @@ func mustEnableIssues(ctx *context.APIContext) {
ctx.Repo.Permission)
}
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
@@ -670,7 +697,7 @@ func mustAllowPulls(ctx *context.APIContext) {
ctx.Repo.Permission)
}
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
@@ -696,28 +723,36 @@ func mustEnableIssuesOrPulls(ctx *context.APIContext) {
ctx.Repo.Permission)
}
}
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
func mustEnableWiki(ctx *context.APIContext) {
if !(ctx.Repo.CanRead(unit.TypeWiki)) {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
+// FIXME: for consistency, maybe most mustNotBeArchived checks should be replaced with mustEnableEditor
func mustNotBeArchived(ctx *context.APIContext) {
if ctx.Repo.Repository.IsArchived {
- ctx.Error(http.StatusLocked, "RepoArchived", fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString()))
+ ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.FullName()))
+ return
+ }
+}
+
+func mustEnableEditor(ctx *context.APIContext) {
+ if !ctx.Repo.Repository.CanEnableEditor() {
+ ctx.APIError(http.StatusLocked, fmt.Errorf("%s is not allowed to edit", ctx.Repo.Repository.FullName()))
return
}
}
func mustEnableAttachments(ctx *context.APIContext) {
if !setting.Attachment.Enabled {
- ctx.NotFound()
+ ctx.APIErrorNotFound()
return
}
}
@@ -728,7 +763,7 @@ func bind[T any](_ T) any {
theObj := new(T) // create a new form obj for every request but not use obj directly
errs := binding.Bind(ctx.Req, theObj)
if len(errs) > 0 {
- ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
+ ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
return
}
web.SetForm(ctx, theObj)
@@ -756,7 +791,7 @@ func apiAuth(authMethod auth.Method) func(*context.APIContext) {
return func(ctx *context.APIContext) {
ar, err := common.AuthShared(ctx.Base, nil, authMethod)
if err != nil {
- ctx.Error(http.StatusUnauthorized, "APIAuth", err)
+ ctx.APIError(http.StatusUnauthorized, err)
return
}
ctx.Doer = ar.Doer
@@ -830,15 +865,15 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
func individualPermsChecker(ctx *context.APIContext) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
- switch {
- case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
+ switch ctx.ContextUser.Visibility {
+ case api.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
- ctx.NotFound("Visit Project", nil)
+ ctx.APIErrorNotFound("Visit Project", nil)
return
}
- case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
+ case api.VisibleTypeLimited:
if ctx.Doer == nil {
- ctx.NotFound("Visit Project", nil)
+ ctx.APIErrorNotFound("Visit Project", nil)
return
}
}
@@ -874,7 +909,7 @@ func Routes() *web.Router {
m.Use(apiAuth(buildAuthGroup()))
m.Use(verifyAuthWithOptions(&common.VerifyOptions{
- SignInRequired: setting.Service.RequireSignInView,
+ SignInRequired: setting.Service.RequireSignInViewStrict,
}))
addActionsRoutes := func(
@@ -900,8 +935,14 @@ func Routes() *web.Router {
})
m.Group("/runners", func() {
+ m.Get("", reqToken(), reqChecker, act.ListRunners)
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
+ m.Post("/registration-token", reqToken(), reqChecker, act.CreateRegistrationToken)
+ m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
+ m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
})
+ m.Get("/runs", reqToken(), reqChecker, act.ListWorkflowRuns)
+ m.Get("/jobs", reqToken(), reqChecker, act.ListWorkflowJobs)
})
}
@@ -931,7 +972,8 @@ func Routes() *web.Router {
// Misc (public accessible)
m.Group("", func() {
m.Get("/version", misc.Version)
- m.Get("/signing-key.gpg", misc.SigningKey)
+ m.Get("/signing-key.gpg", misc.SigningKeyGPG)
+ m.Get("/signing-key.pub", misc.SigningKeySSH)
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
@@ -995,7 +1037,7 @@ func Routes() *web.Router {
m.Get("/{target}", user.CheckFollowing)
})
- m.Get("/starred", user.GetStarredRepos)
+ m.Get("/starred", reqStarsEnabled(), user.GetStarredRepos)
m.Get("/subscriptions", user.GetWatchedRepos)
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
@@ -1031,8 +1073,15 @@ func Routes() *web.Router {
})
m.Group("/runners", func() {
+ m.Get("", reqToken(), user.ListRunners)
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
+ m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
+ m.Get("/{runner_id}", reqToken(), user.GetRunner)
+ m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
})
+
+ m.Get("/runs", reqToken(), user.ListWorkflowRuns)
+ m.Get("/jobs", reqToken(), user.ListWorkflowJobs)
})
m.Get("/followers", user.ListMyFollowers)
@@ -1086,7 +1135,7 @@ func Routes() *web.Router {
m.Put("", user.Star)
m.Delete("", user.Unstar)
}, repoAssignment(), checkTokenPublicOnly())
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
+ }, reqStarsEnabled(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
m.Get("/times", repo.ListMyTrackedTimes)
m.Get("/stopwatches", repo.GetStopwatches)
m.Get("/subscriptions", user.GetMyWatchedRepos)
@@ -1145,11 +1194,22 @@ func Routes() *web.Router {
m.Post("/accept", repo.AcceptTransfer)
m.Post("/reject", repo.RejectTransfer)
}, reqToken())
- addActionsRoutes(
- m,
- reqOwner(),
- repo.NewAction(),
- )
+
+ addActionsRoutes(m, reqOwner(), repo.NewAction()) // it adds the routes for secrets/variables and runner management
+
+ m.Group("/actions/workflows", func() {
+ m.Get("", repo.ActionsListRepositoryWorkflows)
+ m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
+ m.Put("/{workflow_id}/disable", reqRepoWriter(unit.TypeActions), repo.ActionsDisableWorkflow)
+ m.Put("/{workflow_id}/enable", reqRepoWriter(unit.TypeActions), repo.ActionsEnableWorkflow)
+ m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
+ }, context.ReferencesGitRepo(), reqToken(), reqRepoReader(unit.TypeActions))
+
+ m.Group("/actions/jobs", func() {
+ m.Get("/{job_id}", repo.GetWorkflowJob)
+ m.Get("/{job_id}/logs", repo.DownloadActionsRunJobLogs)
+ }, reqToken(), reqRepoReader(unit.TypeActions))
+
m.Group("/hooks/git", func() {
m.Combo("").Get(repo.ListGitHooks)
m.Group("/{id}", func() {
@@ -1187,7 +1247,7 @@ func Routes() *web.Router {
}, reqToken())
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
- m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
+ m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
m.Combo("/forks").Get(repo.ListForks).
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
@@ -1225,6 +1285,20 @@ func Routes() *web.Router {
}, reqToken(), reqAdmin())
m.Group("/actions", func() {
m.Get("/tasks", repo.ListActionTasks)
+ m.Group("/runs", func() {
+ m.Group("/{run}", func() {
+ m.Get("", repo.GetWorkflowRun)
+ m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
+ m.Get("/jobs", repo.ListWorkflowRunJobs)
+ m.Get("/artifacts", repo.GetArtifactsOfRun)
+ })
+ })
+ m.Get("/artifacts", repo.GetArtifacts)
+ m.Group("/artifacts/{artifact_id}", func() {
+ m.Get("", repo.GetArtifact)
+ m.Delete("", reqRepoWriter(unit.TypeActions), repo.DeleteArtifact)
+ })
+ m.Get("/artifacts/{artifact_id}/zip", repo.DownloadArtifact)
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys).
@@ -1248,7 +1322,7 @@ func Routes() *web.Router {
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
- m.Get("/stargazers", repo.ListStargazers)
+ m.Get("/stargazers", reqStarsEnabled(), repo.ListStargazers)
m.Get("/subscribers", repo.ListSubscribers)
m.Group("/subscription", func() {
m.Get("", user.IsWatching)
@@ -1349,18 +1423,29 @@ func Routes() *web.Router {
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
m.Get("/notes/{sha}", repo.GetNote)
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
- m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
m.Group("/contents", func() {
m.Get("", repo.GetContentsList)
- m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
m.Get("/*", repo.GetContents)
- m.Group("/*", func() {
- m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
- m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
- m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
- }, reqToken())
- }, reqRepoReader(unit.TypeCode))
- m.Get("/signing-key.gpg", misc.SigningKey)
+ m.Group("", func() {
+ // "change file" operations, need permission to write to the target branch provided by the form
+ m.Post("", bind(api.ChangeFilesOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ChangeFiles)
+ m.Group("/*", func() {
+ m.Post("", bind(api.CreateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.CreateFile)
+ m.Put("", bind(api.UpdateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.UpdateFile)
+ m.Delete("", bind(api.DeleteFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.DeleteFile)
+ })
+ m.Post("/diffpatch", bind(api.ApplyDiffPatchFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ApplyDiffPatch)
+ }, mustEnableEditor, reqToken())
+ }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
+ m.Group("/contents-ext", func() {
+ m.Get("", repo.GetContentsExt)
+ m.Get("/*", repo.GetContentsExt)
+ }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
+ m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()).
+ Get(repo.GetFileContentsGet).
+ Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // the POST method requires "write" permission, so we also support "GET" method above
+ m.Get("/signing-key.gpg", misc.SigningKeyGPG)
+ m.Get("/signing-key.pub", misc.SigningKeySSH)
m.Group("/topics", func() {
m.Combo("").Get(repo.ListTopics).
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
@@ -1381,10 +1466,14 @@ func Routes() *web.Router {
m.Delete("", repo.DeleteAvatar)
}, reqAdmin(), reqToken())
- m.Get("/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
+ m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
}, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
+ // Artifacts direct download endpoint authenticates via signed url
+ // it is protected by the "sig" parameter (to help to access private repo), so no need to use other middlewares
+ m.Get("/repos/{username}/{reponame}/actions/artifacts/{artifact_id}/zip/raw", repo.DownloadArtifactRaw)
+
// Notifications (requires notifications scope)
m.Group("/repos", func() {
m.Group("/{username}/{reponame}", func() {
@@ -1489,6 +1578,11 @@ func Routes() *web.Router {
Delete(reqToken(), reqAdmin(), repo.UnpinIssue)
m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin)
})
+ m.Group("/lock", func() {
+ m.Combo("").
+ Put(bind(api.LockIssueOption{}), repo.LockIssue).
+ Delete(repo.UnlockIssue)
+ }, reqToken(), reqAdmin())
})
}, mustEnableIssuesOrPulls)
m.Group("/labels", func() {
@@ -1510,13 +1604,24 @@ func Routes() *web.Router {
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
m.Group("/packages/{username}", func() {
- m.Group("/{type}/{name}/{version}", func() {
- m.Get("", reqToken(), packages.GetPackage)
- m.Delete("", reqToken(), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
- m.Get("/files", reqToken(), packages.ListPackageFiles)
+ m.Group("/{type}/{name}", func() {
+ m.Get("/", packages.ListPackageVersions)
+
+ m.Group("/{version}", func() {
+ m.Get("", packages.GetPackage)
+ m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
+ m.Get("/files", packages.ListPackageFiles)
+ })
+
+ m.Group("/-", func() {
+ m.Get("/latest", packages.GetLatestPackageVersion)
+ m.Post("/link/{repo_name}", reqPackageAccess(perm.AccessModeWrite), packages.LinkPackage)
+ m.Post("/unlink", reqPackageAccess(perm.AccessModeWrite), packages.UnlinkPackage)
+ })
})
- m.Get("/", reqToken(), packages.ListPackages)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
+
+ m.Get("/", packages.ListPackages)
+ }, reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
// Organizations
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
@@ -1530,6 +1635,7 @@ func Routes() *web.Router {
m.Combo("").Get(org.Get).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
Delete(reqToken(), reqOrgOwnership(), org.Delete)
+ m.Post("/rename", reqToken(), reqOrgOwnership(), bind(api.RenameOrgOption{}), org.Rename)
m.Combo("/repos").Get(user.ListOrgRepos).
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
m.Group("/members", func() {
@@ -1644,6 +1750,16 @@ func Routes() *web.Router {
Patch(bind(api.EditHookOption{}), admin.EditHook).
Delete(admin.DeleteHook)
})
+ m.Group("/actions", func() {
+ m.Group("/runners", func() {
+ m.Get("", admin.ListRunners)
+ m.Post("/registration-token", admin.CreateRegistrationToken)
+ m.Get("/{runner_id}", admin.GetRunner)
+ m.Delete("/{runner_id}", admin.DeleteRunner)
+ })
+ m.Get("/runs", admin.ListWorkflowRuns)
+ m.Get("/jobs", admin.ListWorkflowJobs)
+ })
m.Group("/runners", func() {
m.Get("/registration-token", admin.GetRegistrationToken)
})