code.gitea.io/gitea@v1.22.3/routers/api/v1/api.go (about)

     1  // Copyright 2015 The Gogs Authors. All rights reserved.
     2  // Copyright 2016 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  // Package v1 Gitea API
     6  //
     7  // This documentation describes the Gitea API.
     8  //
     9  //	Schemes: https, http
    10  //	BasePath: /api/v1
    11  //	Version: {{AppVer | JSEscape}}
    12  //	License: MIT http://opensource.org/licenses/MIT
    13  //
    14  //	Consumes:
    15  //	- application/json
    16  //	- text/plain
    17  //
    18  //	Produces:
    19  //	- application/json
    20  //	- text/html
    21  //
    22  //	Security:
    23  //	- BasicAuth :
    24  //	- Token :
    25  //	- AccessToken :
    26  //	- AuthorizationHeaderToken :
    27  //	- SudoParam :
    28  //	- SudoHeader :
    29  //	- TOTPHeader :
    30  //
    31  //	SecurityDefinitions:
    32  //	BasicAuth:
    33  //	     type: basic
    34  //	Token:
    35  //	     type: apiKey
    36  //	     name: token
    37  //	     in: query
    38  //	     description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
    39  //	AccessToken:
    40  //	     type: apiKey
    41  //	     name: access_token
    42  //	     in: query
    43  //	     description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
    44  //	AuthorizationHeaderToken:
    45  //	     type: apiKey
    46  //	     name: Authorization
    47  //	     in: header
    48  //	     description: API tokens must be prepended with "token" followed by a space.
    49  //	SudoParam:
    50  //	     type: apiKey
    51  //	     name: sudo
    52  //	     in: query
    53  //	     description: Sudo API request as the user provided as the key. Admin privileges are required.
    54  //	SudoHeader:
    55  //	     type: apiKey
    56  //	     name: Sudo
    57  //	     in: header
    58  //	     description: Sudo API request as the user provided as the key. Admin privileges are required.
    59  //	TOTPHeader:
    60  //	     type: apiKey
    61  //	     name: X-GITEA-OTP
    62  //	     in: header
    63  //	     description: Must be used in combination with BasicAuth if two-factor authentication is enabled.
    64  //
    65  // swagger:meta
    66  package v1
    67  
    68  import (
    69  	"fmt"
    70  	"net/http"
    71  	"strings"
    72  
    73  	actions_model "code.gitea.io/gitea/models/actions"
    74  	auth_model "code.gitea.io/gitea/models/auth"
    75  	"code.gitea.io/gitea/models/db"
    76  	"code.gitea.io/gitea/models/organization"
    77  	"code.gitea.io/gitea/models/perm"
    78  	access_model "code.gitea.io/gitea/models/perm/access"
    79  	repo_model "code.gitea.io/gitea/models/repo"
    80  	"code.gitea.io/gitea/models/unit"
    81  	user_model "code.gitea.io/gitea/models/user"
    82  	"code.gitea.io/gitea/modules/log"
    83  	"code.gitea.io/gitea/modules/setting"
    84  	api "code.gitea.io/gitea/modules/structs"
    85  	"code.gitea.io/gitea/modules/web"
    86  	"code.gitea.io/gitea/routers/api/v1/activitypub"
    87  	"code.gitea.io/gitea/routers/api/v1/admin"
    88  	"code.gitea.io/gitea/routers/api/v1/misc"
    89  	"code.gitea.io/gitea/routers/api/v1/notify"
    90  	"code.gitea.io/gitea/routers/api/v1/org"
    91  	"code.gitea.io/gitea/routers/api/v1/packages"
    92  	"code.gitea.io/gitea/routers/api/v1/repo"
    93  	"code.gitea.io/gitea/routers/api/v1/settings"
    94  	"code.gitea.io/gitea/routers/api/v1/user"
    95  	"code.gitea.io/gitea/routers/common"
    96  	"code.gitea.io/gitea/services/actions"
    97  	"code.gitea.io/gitea/services/auth"
    98  	"code.gitea.io/gitea/services/context"
    99  	"code.gitea.io/gitea/services/forms"
   100  
   101  	_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
   102  
   103  	"gitea.com/go-chi/binding"
   104  	"github.com/go-chi/cors"
   105  )
   106  
   107  func sudo() func(ctx *context.APIContext) {
   108  	return func(ctx *context.APIContext) {
   109  		sudo := ctx.FormString("sudo")
   110  		if len(sudo) == 0 {
   111  			sudo = ctx.Req.Header.Get("Sudo")
   112  		}
   113  
   114  		if len(sudo) > 0 {
   115  			if ctx.IsSigned && ctx.Doer.IsAdmin {
   116  				user, err := user_model.GetUserByName(ctx, sudo)
   117  				if err != nil {
   118  					if user_model.IsErrUserNotExist(err) {
   119  						ctx.NotFound()
   120  					} else {
   121  						ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   122  					}
   123  					return
   124  				}
   125  				log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name)
   126  				ctx.Doer = user
   127  			} else {
   128  				ctx.JSON(http.StatusForbidden, map[string]string{
   129  					"message": "Only administrators allowed to sudo.",
   130  				})
   131  				return
   132  			}
   133  		}
   134  	}
   135  }
   136  
   137  func repoAssignment() func(ctx *context.APIContext) {
   138  	return func(ctx *context.APIContext) {
   139  		userName := ctx.Params("username")
   140  		repoName := ctx.Params("reponame")
   141  
   142  		var (
   143  			owner *user_model.User
   144  			err   error
   145  		)
   146  
   147  		// Check if the user is the same as the repository owner.
   148  		if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
   149  			owner = ctx.Doer
   150  		} else {
   151  			owner, err = user_model.GetUserByName(ctx, userName)
   152  			if err != nil {
   153  				if user_model.IsErrUserNotExist(err) {
   154  					if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
   155  						context.RedirectToUser(ctx.Base, userName, redirectUserID)
   156  					} else if user_model.IsErrUserRedirectNotExist(err) {
   157  						ctx.NotFound("GetUserByName", err)
   158  					} else {
   159  						ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
   160  					}
   161  				} else {
   162  					ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   163  				}
   164  				return
   165  			}
   166  		}
   167  		ctx.Repo.Owner = owner
   168  		ctx.ContextUser = owner
   169  
   170  		// Get repository.
   171  		repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName)
   172  		if err != nil {
   173  			if repo_model.IsErrRepoNotExist(err) {
   174  				redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, repoName)
   175  				if err == nil {
   176  					context.RedirectToRepo(ctx.Base, redirectRepoID)
   177  				} else if repo_model.IsErrRedirectNotExist(err) {
   178  					ctx.NotFound()
   179  				} else {
   180  					ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err)
   181  				}
   182  			} else {
   183  				ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
   184  			}
   185  			return
   186  		}
   187  
   188  		repo.Owner = owner
   189  		ctx.Repo.Repository = repo
   190  
   191  		if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
   192  			taskID := ctx.Data["ActionsTaskID"].(int64)
   193  			task, err := actions_model.GetTaskByID(ctx, taskID)
   194  			if err != nil {
   195  				ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err)
   196  				return
   197  			}
   198  			if task.RepoID != repo.ID {
   199  				ctx.NotFound()
   200  				return
   201  			}
   202  
   203  			if task.IsForkPullRequest {
   204  				ctx.Repo.Permission.AccessMode = perm.AccessModeRead
   205  			} else {
   206  				ctx.Repo.Permission.AccessMode = perm.AccessModeWrite
   207  			}
   208  
   209  			if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
   210  				ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
   211  				return
   212  			}
   213  			ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
   214  		} else {
   215  			ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
   216  			if err != nil {
   217  				ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
   218  				return
   219  			}
   220  		}
   221  
   222  		if !ctx.Repo.Permission.HasAnyUnitAccess() {
   223  			ctx.NotFound()
   224  			return
   225  		}
   226  	}
   227  }
   228  
   229  func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
   230  	return func(ctx *context.APIContext) {
   231  		if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
   232  			ctx.Error(http.StatusForbidden, "reqPackageAccess", "user should have specific permission or be a site admin")
   233  			return
   234  		}
   235  	}
   236  }
   237  
   238  func checkTokenPublicOnly() func(ctx *context.APIContext) {
   239  	return func(ctx *context.APIContext) {
   240  		if !ctx.PublicOnly {
   241  			return
   242  		}
   243  
   244  		requiredScopeCategories, ok := ctx.Data["requiredScopeCategories"].([]auth_model.AccessTokenScopeCategory)
   245  		if !ok || len(requiredScopeCategories) == 0 {
   246  			return
   247  		}
   248  
   249  		// public Only permission check
   250  		switch {
   251  		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
   252  			if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
   253  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
   254  				return
   255  			}
   256  		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
   257  			if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
   258  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
   259  				return
   260  			}
   261  		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
   262  			if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
   263  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
   264  				return
   265  			}
   266  			if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
   267  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
   268  				return
   269  			}
   270  		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
   271  			if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
   272  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
   273  				return
   274  			}
   275  		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
   276  			if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
   277  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
   278  				return
   279  			}
   280  		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
   281  			if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
   282  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
   283  				return
   284  			}
   285  		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
   286  			if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
   287  				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
   288  				return
   289  			}
   290  		}
   291  	}
   292  }
   293  
   294  // if a token is being used for auth, we check that it contains the required scope
   295  // if a token is not being used, reqToken will enforce other sign in methods
   296  func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
   297  	return func(ctx *context.APIContext) {
   298  		// no scope required
   299  		if len(requiredScopeCategories) == 0 {
   300  			return
   301  		}
   302  
   303  		// Need OAuth2 token to be present.
   304  		scope, scopeExists := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
   305  		if ctx.Data["IsApiToken"] != true || !scopeExists {
   306  			return
   307  		}
   308  
   309  		// use the http method to determine the access level
   310  		requiredScopeLevel := auth_model.Read
   311  		if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
   312  			requiredScopeLevel = auth_model.Write
   313  		}
   314  
   315  		// get the required scope for the given access level and category
   316  		requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
   317  		allow, err := scope.HasScope(requiredScopes...)
   318  		if err != nil {
   319  			ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
   320  			return
   321  		}
   322  
   323  		if !allow {
   324  			ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
   325  			return
   326  		}
   327  
   328  		ctx.Data["requiredScopeCategories"] = requiredScopeCategories
   329  
   330  		// check if scope only applies to public resources
   331  		publicOnly, err := scope.PublicOnly()
   332  		if err != nil {
   333  			ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
   334  			return
   335  		}
   336  
   337  		// assign to true so that those searching should only filter public repositories/users/organizations
   338  		ctx.PublicOnly = publicOnly
   339  	}
   340  }
   341  
   342  // Contexter middleware already checks token for user sign in process.
   343  func reqToken() func(ctx *context.APIContext) {
   344  	return func(ctx *context.APIContext) {
   345  		// If actions token is present
   346  		if true == ctx.Data["IsActionsToken"] {
   347  			return
   348  		}
   349  
   350  		if ctx.IsSigned {
   351  			return
   352  		}
   353  		ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
   354  	}
   355  }
   356  
   357  func reqExploreSignIn() func(ctx *context.APIContext) {
   358  	return func(ctx *context.APIContext) {
   359  		if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
   360  			ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
   361  		}
   362  	}
   363  }
   364  
   365  func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
   366  	return func(ctx *context.APIContext) {
   367  		if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
   368  			return
   369  		}
   370  		if !ctx.IsBasicAuth {
   371  			ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
   372  			return
   373  		}
   374  	}
   375  }
   376  
   377  // reqSiteAdmin user should be the site admin
   378  func reqSiteAdmin() func(ctx *context.APIContext) {
   379  	return func(ctx *context.APIContext) {
   380  		if !ctx.IsUserSiteAdmin() {
   381  			ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
   382  			return
   383  		}
   384  	}
   385  }
   386  
   387  // reqOwner user should be the owner of the repo or site admin.
   388  func reqOwner() func(ctx *context.APIContext) {
   389  	return func(ctx *context.APIContext) {
   390  		if !ctx.Repo.IsOwner() && !ctx.IsUserSiteAdmin() {
   391  			ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
   392  			return
   393  		}
   394  	}
   395  }
   396  
   397  // reqSelfOrAdmin doer should be the same as the contextUser or site admin
   398  func reqSelfOrAdmin() func(ctx *context.APIContext) {
   399  	return func(ctx *context.APIContext) {
   400  		if !ctx.IsUserSiteAdmin() && ctx.ContextUser != ctx.Doer {
   401  			ctx.Error(http.StatusForbidden, "reqSelfOrAdmin", "doer should be the site admin or be same as the contextUser")
   402  			return
   403  		}
   404  	}
   405  }
   406  
   407  // reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin
   408  func reqAdmin() func(ctx *context.APIContext) {
   409  	return func(ctx *context.APIContext) {
   410  		if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
   411  			ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
   412  			return
   413  		}
   414  	}
   415  }
   416  
   417  // reqRepoWriter user should have a permission to write to a repo, or be a site admin
   418  func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
   419  	return func(ctx *context.APIContext) {
   420  		if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
   421  			ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
   422  			return
   423  		}
   424  	}
   425  }
   426  
   427  // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
   428  func reqRepoBranchWriter(ctx *context.APIContext) {
   429  	options, ok := web.GetForm(ctx).(api.FileOptionInterface)
   430  	if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
   431  		ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch")
   432  		return
   433  	}
   434  }
   435  
   436  // reqRepoReader user should have specific read permission or be a repo admin or a site admin
   437  func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
   438  	return func(ctx *context.APIContext) {
   439  		if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
   440  			ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
   441  			return
   442  		}
   443  	}
   444  }
   445  
   446  // reqAnyRepoReader user should have any permission to read repository or permissions of site admin
   447  func reqAnyRepoReader() func(ctx *context.APIContext) {
   448  	return func(ctx *context.APIContext) {
   449  		if !ctx.Repo.Permission.HasAnyUnitAccess() && !ctx.IsUserSiteAdmin() {
   450  			ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
   451  			return
   452  		}
   453  	}
   454  }
   455  
   456  // reqOrgOwnership user should be an organization owner, or a site admin
   457  func reqOrgOwnership() func(ctx *context.APIContext) {
   458  	return func(ctx *context.APIContext) {
   459  		if ctx.IsUserSiteAdmin() {
   460  			return
   461  		}
   462  
   463  		var orgID int64
   464  		if ctx.Org.Organization != nil {
   465  			orgID = ctx.Org.Organization.ID
   466  		} else if ctx.Org.Team != nil {
   467  			orgID = ctx.Org.Team.OrgID
   468  		} else {
   469  			ctx.Error(http.StatusInternalServerError, "", "reqOrgOwnership: unprepared context")
   470  			return
   471  		}
   472  
   473  		isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
   474  		if err != nil {
   475  			ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
   476  			return
   477  		} else if !isOwner {
   478  			if ctx.Org.Organization != nil {
   479  				ctx.Error(http.StatusForbidden, "", "Must be an organization owner")
   480  			} else {
   481  				ctx.NotFound()
   482  			}
   483  			return
   484  		}
   485  	}
   486  }
   487  
   488  // reqTeamMembership user should be an team member, or a site admin
   489  func reqTeamMembership() func(ctx *context.APIContext) {
   490  	return func(ctx *context.APIContext) {
   491  		if ctx.IsUserSiteAdmin() {
   492  			return
   493  		}
   494  		if ctx.Org.Team == nil {
   495  			ctx.Error(http.StatusInternalServerError, "", "reqTeamMembership: unprepared context")
   496  			return
   497  		}
   498  
   499  		orgID := ctx.Org.Team.OrgID
   500  		isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
   501  		if err != nil {
   502  			ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
   503  			return
   504  		} else if isOwner {
   505  			return
   506  		}
   507  
   508  		if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil {
   509  			ctx.Error(http.StatusInternalServerError, "IsTeamMember", err)
   510  			return
   511  		} else if !isTeamMember {
   512  			isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
   513  			if err != nil {
   514  				ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
   515  			} else if isOrgMember {
   516  				ctx.Error(http.StatusForbidden, "", "Must be a team member")
   517  			} else {
   518  				ctx.NotFound()
   519  			}
   520  			return
   521  		}
   522  	}
   523  }
   524  
   525  // reqOrgMembership user should be an organization member, or a site admin
   526  func reqOrgMembership() func(ctx *context.APIContext) {
   527  	return func(ctx *context.APIContext) {
   528  		if ctx.IsUserSiteAdmin() {
   529  			return
   530  		}
   531  
   532  		var orgID int64
   533  		if ctx.Org.Organization != nil {
   534  			orgID = ctx.Org.Organization.ID
   535  		} else if ctx.Org.Team != nil {
   536  			orgID = ctx.Org.Team.OrgID
   537  		} else {
   538  			ctx.Error(http.StatusInternalServerError, "", "reqOrgMembership: unprepared context")
   539  			return
   540  		}
   541  
   542  		if isMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID); err != nil {
   543  			ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
   544  			return
   545  		} else if !isMember {
   546  			if ctx.Org.Organization != nil {
   547  				ctx.Error(http.StatusForbidden, "", "Must be an organization member")
   548  			} else {
   549  				ctx.NotFound()
   550  			}
   551  			return
   552  		}
   553  	}
   554  }
   555  
   556  func reqGitHook() func(ctx *context.APIContext) {
   557  	return func(ctx *context.APIContext) {
   558  		if !ctx.Doer.CanEditGitHook() {
   559  			ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
   560  			return
   561  		}
   562  	}
   563  }
   564  
   565  // reqWebhooksEnabled requires webhooks to be enabled by admin.
   566  func reqWebhooksEnabled() func(ctx *context.APIContext) {
   567  	return func(ctx *context.APIContext) {
   568  		if setting.DisableWebhooks {
   569  			ctx.Error(http.StatusForbidden, "", "webhooks disabled by administrator")
   570  			return
   571  		}
   572  	}
   573  }
   574  
   575  func orgAssignment(args ...bool) func(ctx *context.APIContext) {
   576  	var (
   577  		assignOrg  bool
   578  		assignTeam bool
   579  	)
   580  	if len(args) > 0 {
   581  		assignOrg = args[0]
   582  	}
   583  	if len(args) > 1 {
   584  		assignTeam = args[1]
   585  	}
   586  	return func(ctx *context.APIContext) {
   587  		ctx.Org = new(context.APIOrganization)
   588  
   589  		var err error
   590  		if assignOrg {
   591  			ctx.Org.Organization, err = organization.GetOrgByName(ctx, ctx.Params(":org"))
   592  			if err != nil {
   593  				if organization.IsErrOrgNotExist(err) {
   594  					redirectUserID, err := user_model.LookupUserRedirect(ctx, ctx.Params(":org"))
   595  					if err == nil {
   596  						context.RedirectToUser(ctx.Base, ctx.Params(":org"), redirectUserID)
   597  					} else if user_model.IsErrUserRedirectNotExist(err) {
   598  						ctx.NotFound("GetOrgByName", err)
   599  					} else {
   600  						ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
   601  					}
   602  				} else {
   603  					ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
   604  				}
   605  				return
   606  			}
   607  			ctx.ContextUser = ctx.Org.Organization.AsUser()
   608  		}
   609  
   610  		if assignTeam {
   611  			ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.ParamsInt64(":teamid"))
   612  			if err != nil {
   613  				if organization.IsErrTeamNotExist(err) {
   614  					ctx.NotFound()
   615  				} else {
   616  					ctx.Error(http.StatusInternalServerError, "GetTeamById", err)
   617  				}
   618  				return
   619  			}
   620  		}
   621  	}
   622  }
   623  
   624  func mustEnableIssues(ctx *context.APIContext) {
   625  	if !ctx.Repo.CanRead(unit.TypeIssues) {
   626  		if log.IsTrace() {
   627  			if ctx.IsSigned {
   628  				log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
   629  					"User in Repo has Permissions: %-+v",
   630  					ctx.Doer,
   631  					unit.TypeIssues,
   632  					ctx.Repo.Repository,
   633  					ctx.Repo.Permission)
   634  			} else {
   635  				log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
   636  					"Anonymous user in Repo has Permissions: %-+v",
   637  					unit.TypeIssues,
   638  					ctx.Repo.Repository,
   639  					ctx.Repo.Permission)
   640  			}
   641  		}
   642  		ctx.NotFound()
   643  		return
   644  	}
   645  }
   646  
   647  func mustAllowPulls(ctx *context.APIContext) {
   648  	if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
   649  		if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
   650  			if ctx.IsSigned {
   651  				log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
   652  					"User in Repo has Permissions: %-+v",
   653  					ctx.Doer,
   654  					unit.TypePullRequests,
   655  					ctx.Repo.Repository,
   656  					ctx.Repo.Permission)
   657  			} else {
   658  				log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
   659  					"Anonymous user in Repo has Permissions: %-+v",
   660  					unit.TypePullRequests,
   661  					ctx.Repo.Repository,
   662  					ctx.Repo.Permission)
   663  			}
   664  		}
   665  		ctx.NotFound()
   666  		return
   667  	}
   668  }
   669  
   670  func mustEnableIssuesOrPulls(ctx *context.APIContext) {
   671  	if !ctx.Repo.CanRead(unit.TypeIssues) &&
   672  		!(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
   673  		if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
   674  			if ctx.IsSigned {
   675  				log.Trace("Permission Denied: User %-v cannot read %-v and %-v in Repo %-v\n"+
   676  					"User in Repo has Permissions: %-+v",
   677  					ctx.Doer,
   678  					unit.TypeIssues,
   679  					unit.TypePullRequests,
   680  					ctx.Repo.Repository,
   681  					ctx.Repo.Permission)
   682  			} else {
   683  				log.Trace("Permission Denied: Anonymous user cannot read %-v and %-v in Repo %-v\n"+
   684  					"Anonymous user in Repo has Permissions: %-+v",
   685  					unit.TypeIssues,
   686  					unit.TypePullRequests,
   687  					ctx.Repo.Repository,
   688  					ctx.Repo.Permission)
   689  			}
   690  		}
   691  		ctx.NotFound()
   692  		return
   693  	}
   694  }
   695  
   696  func mustEnableWiki(ctx *context.APIContext) {
   697  	if !(ctx.Repo.CanRead(unit.TypeWiki)) {
   698  		ctx.NotFound()
   699  		return
   700  	}
   701  }
   702  
   703  func mustNotBeArchived(ctx *context.APIContext) {
   704  	if ctx.Repo.Repository.IsArchived {
   705  		ctx.Error(http.StatusLocked, "RepoArchived", fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString()))
   706  		return
   707  	}
   708  }
   709  
   710  func mustEnableAttachments(ctx *context.APIContext) {
   711  	if !setting.Attachment.Enabled {
   712  		ctx.NotFound()
   713  		return
   714  	}
   715  }
   716  
   717  // bind binding an obj to a func(ctx *context.APIContext)
   718  func bind[T any](_ T) any {
   719  	return func(ctx *context.APIContext) {
   720  		theObj := new(T) // create a new form obj for every request but not use obj directly
   721  		errs := binding.Bind(ctx.Req, theObj)
   722  		if len(errs) > 0 {
   723  			ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
   724  			return
   725  		}
   726  		web.SetForm(ctx, theObj)
   727  	}
   728  }
   729  
   730  func buildAuthGroup() *auth.Group {
   731  	group := auth.NewGroup(
   732  		&auth.OAuth2{},
   733  		&auth.HTTPSign{},
   734  		&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
   735  	)
   736  	if setting.Service.EnableReverseProxyAuthAPI {
   737  		group.Add(&auth.ReverseProxy{})
   738  	}
   739  
   740  	if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) {
   741  		group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
   742  	}
   743  
   744  	return group
   745  }
   746  
   747  func apiAuth(authMethod auth.Method) func(*context.APIContext) {
   748  	return func(ctx *context.APIContext) {
   749  		ar, err := common.AuthShared(ctx.Base, nil, authMethod)
   750  		if err != nil {
   751  			ctx.Error(http.StatusUnauthorized, "APIAuth", err)
   752  			return
   753  		}
   754  		ctx.Doer = ar.Doer
   755  		ctx.IsSigned = ar.Doer != nil
   756  		ctx.IsBasicAuth = ar.IsBasicAuth
   757  	}
   758  }
   759  
   760  // verifyAuthWithOptions checks authentication according to options
   761  func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) {
   762  	return func(ctx *context.APIContext) {
   763  		// Check prohibit login users.
   764  		if ctx.IsSigned {
   765  			if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
   766  				ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
   767  				ctx.JSON(http.StatusForbidden, map[string]string{
   768  					"message": "This account is not activated.",
   769  				})
   770  				return
   771  			}
   772  			if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
   773  				log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
   774  				ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
   775  				ctx.JSON(http.StatusForbidden, map[string]string{
   776  					"message": "This account is prohibited from signing in, please contact your site administrator.",
   777  				})
   778  				return
   779  			}
   780  
   781  			if ctx.Doer.MustChangePassword {
   782  				ctx.JSON(http.StatusForbidden, map[string]string{
   783  					"message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
   784  				})
   785  				return
   786  			}
   787  		}
   788  
   789  		// Redirect to dashboard if user tries to visit any non-login page.
   790  		if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
   791  			ctx.Redirect(setting.AppSubURL + "/")
   792  			return
   793  		}
   794  
   795  		if options.SignInRequired {
   796  			if !ctx.IsSigned {
   797  				// Restrict API calls with error message.
   798  				ctx.JSON(http.StatusForbidden, map[string]string{
   799  					"message": "Only signed in user is allowed to call APIs.",
   800  				})
   801  				return
   802  			} else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
   803  				ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
   804  				ctx.JSON(http.StatusForbidden, map[string]string{
   805  					"message": "This account is not activated.",
   806  				})
   807  				return
   808  			}
   809  		}
   810  
   811  		if options.AdminRequired {
   812  			if !ctx.Doer.IsAdmin {
   813  				ctx.JSON(http.StatusForbidden, map[string]string{
   814  					"message": "You have no permission to request for this.",
   815  				})
   816  				return
   817  			}
   818  		}
   819  	}
   820  }
   821  
   822  func individualPermsChecker(ctx *context.APIContext) {
   823  	// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
   824  	if ctx.ContextUser.IsIndividual() {
   825  		switch {
   826  		case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
   827  			if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
   828  				ctx.NotFound("Visit Project", nil)
   829  				return
   830  			}
   831  		case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
   832  			if ctx.Doer == nil {
   833  				ctx.NotFound("Visit Project", nil)
   834  				return
   835  			}
   836  		}
   837  	}
   838  }
   839  
   840  // check for and warn against deprecated authentication options
   841  func checkDeprecatedAuthMethods(ctx *context.APIContext) {
   842  	if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
   843  		ctx.Resp.Header().Set("X-Gitea-Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
   844  	}
   845  }
   846  
   847  // Routes registers all v1 APIs routes to web application.
   848  func Routes() *web.Route {
   849  	m := web.NewRoute()
   850  
   851  	m.Use(securityHeaders())
   852  	if setting.CORSConfig.Enabled {
   853  		m.Use(cors.Handler(cors.Options{
   854  			AllowedOrigins:   setting.CORSConfig.AllowDomain,
   855  			AllowedMethods:   setting.CORSConfig.Methods,
   856  			AllowCredentials: setting.CORSConfig.AllowCredentials,
   857  			AllowedHeaders:   append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
   858  			MaxAge:           int(setting.CORSConfig.MaxAge.Seconds()),
   859  		}))
   860  	}
   861  	m.Use(context.APIContexter())
   862  
   863  	m.Use(checkDeprecatedAuthMethods)
   864  
   865  	// Get user from session if logged in.
   866  	m.Use(apiAuth(buildAuthGroup()))
   867  
   868  	m.Use(verifyAuthWithOptions(&common.VerifyOptions{
   869  		SignInRequired: setting.Service.RequireSignInView,
   870  	}))
   871  
   872  	addActionsRoutes := func(
   873  		m *web.Route,
   874  		reqChecker func(ctx *context.APIContext),
   875  		act actions.API,
   876  	) {
   877  		m.Group("/actions", func() {
   878  			m.Group("/secrets", func() {
   879  				m.Get("", reqToken(), reqChecker, act.ListActionsSecrets)
   880  				m.Combo("/{secretname}").
   881  					Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret).
   882  					Delete(reqToken(), reqChecker, act.DeleteSecret)
   883  			})
   884  
   885  			m.Group("/variables", func() {
   886  				m.Get("", reqToken(), reqChecker, act.ListVariables)
   887  				m.Combo("/{variablename}").
   888  					Get(reqToken(), reqChecker, act.GetVariable).
   889  					Delete(reqToken(), reqChecker, act.DeleteVariable).
   890  					Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable).
   891  					Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable)
   892  			})
   893  
   894  			m.Group("/runners", func() {
   895  				m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
   896  			})
   897  		})
   898  	}
   899  
   900  	m.Group("", func() {
   901  		// Miscellaneous (no scope required)
   902  		if setting.API.EnableSwagger {
   903  			m.Get("/swagger", func(ctx *context.APIContext) {
   904  				ctx.Redirect(setting.AppSubURL + "/api/swagger")
   905  			})
   906  		}
   907  
   908  		if setting.Federation.Enabled {
   909  			m.Get("/nodeinfo", misc.NodeInfo)
   910  			m.Group("/activitypub", func() {
   911  				// deprecated, remove in 1.20, use /user-id/{user-id} instead
   912  				m.Group("/user/{username}", func() {
   913  					m.Get("", activitypub.Person)
   914  					m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
   915  				}, context.UserAssignmentAPI(), checkTokenPublicOnly())
   916  				m.Group("/user-id/{user-id}", func() {
   917  					m.Get("", activitypub.Person)
   918  					m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
   919  				}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
   920  			}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
   921  		}
   922  
   923  		// Misc (public accessible)
   924  		m.Group("", func() {
   925  			m.Get("/version", misc.Version)
   926  			m.Get("/signing-key.gpg", misc.SigningKey)
   927  			m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
   928  			m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
   929  			m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
   930  			m.Get("/gitignore/templates", misc.ListGitignoresTemplates)
   931  			m.Get("/gitignore/templates/{name}", misc.GetGitignoreTemplateInfo)
   932  			m.Get("/licenses", misc.ListLicenseTemplates)
   933  			m.Get("/licenses/{name}", misc.GetLicenseTemplateInfo)
   934  			m.Get("/label/templates", misc.ListLabelTemplates)
   935  			m.Get("/label/templates/{name}", misc.GetLabelTemplate)
   936  
   937  			m.Group("/settings", func() {
   938  				m.Get("/ui", settings.GetGeneralUISettings)
   939  				m.Get("/api", settings.GetGeneralAPISettings)
   940  				m.Get("/attachment", settings.GetGeneralAttachmentSettings)
   941  				m.Get("/repository", settings.GetGeneralRepoSettings)
   942  			})
   943  		})
   944  
   945  		// Notifications (requires 'notifications' scope)
   946  		m.Group("/notifications", func() {
   947  			m.Combo("").
   948  				Get(reqToken(), notify.ListNotifications).
   949  				Put(reqToken(), notify.ReadNotifications)
   950  			m.Get("/new", reqToken(), notify.NewAvailable)
   951  			m.Combo("/threads/{id}").
   952  				Get(reqToken(), notify.GetThread).
   953  				Patch(reqToken(), notify.ReadThread)
   954  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
   955  
   956  		// Users (requires user scope)
   957  		m.Group("/users", func() {
   958  			m.Get("/search", reqExploreSignIn(), user.Search)
   959  
   960  			m.Group("/{username}", func() {
   961  				m.Get("", reqExploreSignIn(), user.GetInfo)
   962  
   963  				if setting.Service.EnableUserHeatmap {
   964  					m.Get("/heatmap", user.GetUserHeatmapData)
   965  				}
   966  
   967  				m.Get("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqExploreSignIn(), user.ListUserRepos)
   968  				m.Group("/tokens", func() {
   969  					m.Combo("").Get(user.ListAccessTokens).
   970  						Post(bind(api.CreateAccessTokenOption{}), reqToken(), user.CreateAccessToken)
   971  					m.Combo("/{id}").Delete(reqToken(), user.DeleteAccessToken)
   972  				}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
   973  
   974  				m.Get("/activities/feeds", user.ListUserActivityFeeds)
   975  			}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
   976  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
   977  
   978  		// Users (requires user scope)
   979  		m.Group("/users", func() {
   980  			m.Group("/{username}", func() {
   981  				m.Get("/keys", user.ListPublicKeys)
   982  				m.Get("/gpg_keys", user.ListGPGKeys)
   983  
   984  				m.Get("/followers", user.ListFollowers)
   985  				m.Group("/following", func() {
   986  					m.Get("", user.ListFollowing)
   987  					m.Get("/{target}", user.CheckFollowing)
   988  				})
   989  
   990  				m.Get("/starred", user.GetStarredRepos)
   991  
   992  				m.Get("/subscriptions", user.GetWatchedRepos)
   993  			}, context.UserAssignmentAPI(), checkTokenPublicOnly())
   994  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
   995  
   996  		// Users (requires user scope)
   997  		m.Group("/user", func() {
   998  			m.Get("", user.GetAuthenticatedUser)
   999  			m.Group("/settings", func() {
  1000  				m.Get("", user.GetUserSettings)
  1001  				m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings)
  1002  			}, reqToken())
  1003  			m.Combo("/emails").
  1004  				Get(user.ListEmails).
  1005  				Post(bind(api.CreateEmailOption{}), user.AddEmail).
  1006  				Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
  1007  
  1008  			// manage user-level actions features
  1009  			m.Group("/actions", func() {
  1010  				m.Group("/secrets", func() {
  1011  					m.Combo("/{secretname}").
  1012  						Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret).
  1013  						Delete(user.DeleteSecret)
  1014  				})
  1015  
  1016  				m.Group("/variables", func() {
  1017  					m.Get("", user.ListVariables)
  1018  					m.Combo("/{variablename}").
  1019  						Get(user.GetVariable).
  1020  						Delete(user.DeleteVariable).
  1021  						Post(bind(api.CreateVariableOption{}), user.CreateVariable).
  1022  						Put(bind(api.UpdateVariableOption{}), user.UpdateVariable)
  1023  				})
  1024  
  1025  				m.Group("/runners", func() {
  1026  					m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
  1027  				})
  1028  			})
  1029  
  1030  			m.Get("/followers", user.ListMyFollowers)
  1031  			m.Group("/following", func() {
  1032  				m.Get("", user.ListMyFollowing)
  1033  				m.Group("/{username}", func() {
  1034  					m.Get("", user.CheckMyFollowing)
  1035  					m.Put("", user.Follow)
  1036  					m.Delete("", user.Unfollow)
  1037  				}, context.UserAssignmentAPI())
  1038  			})
  1039  
  1040  			// (admin:public_key scope)
  1041  			m.Group("/keys", func() {
  1042  				m.Combo("").Get(user.ListMyPublicKeys).
  1043  					Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
  1044  				m.Combo("/{id}").Get(user.GetPublicKey).
  1045  					Delete(user.DeletePublicKey)
  1046  			})
  1047  
  1048  			// (admin:application scope)
  1049  			m.Group("/applications", func() {
  1050  				m.Combo("/oauth2").
  1051  					Get(user.ListOauth2Applications).
  1052  					Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application)
  1053  				m.Combo("/oauth2/{id}").
  1054  					Delete(user.DeleteOauth2Application).
  1055  					Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
  1056  					Get(user.GetOauth2Application)
  1057  			})
  1058  
  1059  			// (admin:gpg_key scope)
  1060  			m.Group("/gpg_keys", func() {
  1061  				m.Combo("").Get(user.ListMyGPGKeys).
  1062  					Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
  1063  				m.Combo("/{id}").Get(user.GetGPGKey).
  1064  					Delete(user.DeleteGPGKey)
  1065  			})
  1066  			m.Get("/gpg_key_token", user.GetVerificationToken)
  1067  			m.Post("/gpg_key_verify", bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
  1068  
  1069  			// (repo scope)
  1070  			m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
  1071  				Post(bind(api.CreateRepoOption{}), repo.Create)
  1072  
  1073  			// (repo scope)
  1074  			m.Group("/starred", func() {
  1075  				m.Get("", user.GetMyStarredRepos)
  1076  				m.Group("/{username}/{reponame}", func() {
  1077  					m.Get("", user.IsStarring)
  1078  					m.Put("", user.Star)
  1079  					m.Delete("", user.Unstar)
  1080  				}, repoAssignment(), checkTokenPublicOnly())
  1081  			}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
  1082  			m.Get("/times", repo.ListMyTrackedTimes)
  1083  			m.Get("/stopwatches", repo.GetStopwatches)
  1084  			m.Get("/subscriptions", user.GetMyWatchedRepos)
  1085  			m.Get("/teams", org.ListUserTeams)
  1086  			m.Group("/hooks", func() {
  1087  				m.Combo("").Get(user.ListHooks).
  1088  					Post(bind(api.CreateHookOption{}), user.CreateHook)
  1089  				m.Combo("/{id}").Get(user.GetHook).
  1090  					Patch(bind(api.EditHookOption{}), user.EditHook).
  1091  					Delete(user.DeleteHook)
  1092  			}, reqWebhooksEnabled())
  1093  
  1094  			m.Group("/avatar", func() {
  1095  				m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
  1096  				m.Delete("", user.DeleteAvatar)
  1097  			})
  1098  
  1099  			m.Group("/blocks", func() {
  1100  				m.Get("", user.ListBlocks)
  1101  				m.Group("/{username}", func() {
  1102  					m.Get("", user.CheckUserBlock)
  1103  					m.Put("", user.BlockUser)
  1104  					m.Delete("", user.UnblockUser)
  1105  				}, context.UserAssignmentAPI(), checkTokenPublicOnly())
  1106  			})
  1107  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
  1108  
  1109  		// Repositories (requires repo scope, org scope)
  1110  		m.Post("/org/{org}/repos",
  1111  			// FIXME: we need org in context
  1112  			tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
  1113  			reqToken(),
  1114  			bind(api.CreateRepoOption{}),
  1115  			repo.CreateOrgRepoDeprecated)
  1116  
  1117  		// requires repo scope
  1118  		// FIXME: Don't expose repository id outside of the system
  1119  		m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
  1120  
  1121  		// Repos (requires repo scope)
  1122  		m.Group("/repos", func() {
  1123  			m.Get("/search", repo.Search)
  1124  
  1125  			// (repo scope)
  1126  			m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
  1127  
  1128  			m.Group("/{username}/{reponame}", func() {
  1129  				m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff)
  1130  
  1131  				m.Combo("").Get(reqAnyRepoReader(), repo.Get).
  1132  					Delete(reqToken(), reqOwner(), repo.Delete).
  1133  					Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
  1134  				m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
  1135  				m.Group("/transfer", func() {
  1136  					m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
  1137  					m.Post("/accept", repo.AcceptTransfer)
  1138  					m.Post("/reject", repo.RejectTransfer)
  1139  				}, reqToken())
  1140  				addActionsRoutes(
  1141  					m,
  1142  					reqOwner(),
  1143  					repo.NewAction(),
  1144  				)
  1145  				m.Group("/hooks/git", func() {
  1146  					m.Combo("").Get(repo.ListGitHooks)
  1147  					m.Group("/{id}", func() {
  1148  						m.Combo("").Get(repo.GetGitHook).
  1149  							Patch(bind(api.EditGitHookOption{}), repo.EditGitHook).
  1150  							Delete(repo.DeleteGitHook)
  1151  					})
  1152  				}, reqToken(), reqAdmin(), reqGitHook(), context.ReferencesGitRepo(true))
  1153  				m.Group("/hooks", func() {
  1154  					m.Combo("").Get(repo.ListHooks).
  1155  						Post(bind(api.CreateHookOption{}), repo.CreateHook)
  1156  					m.Group("/{id}", func() {
  1157  						m.Combo("").Get(repo.GetHook).
  1158  							Patch(bind(api.EditHookOption{}), repo.EditHook).
  1159  							Delete(repo.DeleteHook)
  1160  						m.Post("/tests", context.ReferencesGitRepo(), context.RepoRefForAPI, repo.TestHook)
  1161  					})
  1162  				}, reqToken(), reqAdmin(), reqWebhooksEnabled())
  1163  				m.Group("/collaborators", func() {
  1164  					m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
  1165  					m.Group("/{collaborator}", func() {
  1166  						m.Combo("").Get(reqAnyRepoReader(), repo.IsCollaborator).
  1167  							Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
  1168  							Delete(reqAdmin(), repo.DeleteCollaborator)
  1169  						m.Get("/permission", repo.GetRepoPermissions)
  1170  					})
  1171  				}, reqToken())
  1172  				m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
  1173  				m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
  1174  				m.Group("/teams", func() {
  1175  					m.Get("", reqAnyRepoReader(), repo.ListTeams)
  1176  					m.Combo("/{team}").Get(reqAnyRepoReader(), repo.IsTeam).
  1177  						Put(reqAdmin(), repo.AddTeam).
  1178  						Delete(reqAdmin(), repo.DeleteTeam)
  1179  				}, reqToken())
  1180  				m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
  1181  				m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
  1182  				m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
  1183  				m.Combo("/forks").Get(repo.ListForks).
  1184  					Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
  1185  				m.Group("/branches", func() {
  1186  					m.Get("", repo.ListBranches)
  1187  					m.Get("/*", repo.GetBranch)
  1188  					m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
  1189  					m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
  1190  				}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
  1191  				m.Group("/branch_protections", func() {
  1192  					m.Get("", repo.ListBranchProtections)
  1193  					m.Post("", bind(api.CreateBranchProtectionOption{}), mustNotBeArchived, repo.CreateBranchProtection)
  1194  					m.Group("/{name}", func() {
  1195  						m.Get("", repo.GetBranchProtection)
  1196  						m.Patch("", bind(api.EditBranchProtectionOption{}), mustNotBeArchived, repo.EditBranchProtection)
  1197  						m.Delete("", repo.DeleteBranchProtection)
  1198  					})
  1199  				}, reqToken(), reqAdmin())
  1200  				m.Group("/tags", func() {
  1201  					m.Get("", repo.ListTags)
  1202  					m.Get("/*", repo.GetTag)
  1203  					m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
  1204  					m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
  1205  				}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
  1206  				m.Group("/keys", func() {
  1207  					m.Combo("").Get(repo.ListDeployKeys).
  1208  						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
  1209  					m.Combo("/{id}").Get(repo.GetDeployKey).
  1210  						Delete(repo.DeleteDeploykey)
  1211  				}, reqToken(), reqAdmin())
  1212  				m.Group("/times", func() {
  1213  					m.Combo("").Get(repo.ListTrackedTimesByRepository)
  1214  					m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
  1215  				}, mustEnableIssues, reqToken())
  1216  				m.Group("/wiki", func() {
  1217  					m.Combo("/page/{pageName}").
  1218  						Get(repo.GetWikiPage).
  1219  						Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
  1220  						Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
  1221  					m.Get("/revisions/{pageName}", repo.ListPageRevisions)
  1222  					m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
  1223  					m.Get("/pages", repo.ListWikiPages)
  1224  				}, mustEnableWiki)
  1225  				m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
  1226  				m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
  1227  				m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
  1228  				m.Get("/stargazers", repo.ListStargazers)
  1229  				m.Get("/subscribers", repo.ListSubscribers)
  1230  				m.Group("/subscription", func() {
  1231  					m.Get("", user.IsWatching)
  1232  					m.Put("", user.Watch)
  1233  					m.Delete("", user.Unwatch)
  1234  				}, reqToken())
  1235  				m.Group("/releases", func() {
  1236  					m.Combo("").Get(repo.ListReleases).
  1237  						Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
  1238  					m.Combo("/latest").Get(repo.GetLatestRelease)
  1239  					m.Group("/{id}", func() {
  1240  						m.Combo("").Get(repo.GetRelease).
  1241  							Patch(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease).
  1242  							Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteRelease)
  1243  						m.Group("/assets", func() {
  1244  							m.Combo("").Get(repo.ListReleaseAttachments).
  1245  								Post(reqToken(), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment)
  1246  							m.Combo("/{attachment_id}").Get(repo.GetReleaseAttachment).
  1247  								Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
  1248  								Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment)
  1249  						})
  1250  					})
  1251  					m.Group("/tags", func() {
  1252  						m.Combo("/{tag}").
  1253  							Get(repo.GetReleaseByTag).
  1254  							Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
  1255  					})
  1256  				}, reqRepoReader(unit.TypeReleases))
  1257  				m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.MirrorSync)
  1258  				m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), mustNotBeArchived, repo.PushMirrorSync)
  1259  				m.Group("/push_mirrors", func() {
  1260  					m.Combo("").Get(repo.ListPushMirrors).
  1261  						Post(mustNotBeArchived, bind(api.CreatePushMirrorOption{}), repo.AddPushMirror)
  1262  					m.Combo("/{name}").
  1263  						Delete(mustNotBeArchived, repo.DeletePushMirrorByRemoteName).
  1264  						Get(repo.GetPushMirrorByName)
  1265  				}, reqAdmin(), reqToken())
  1266  
  1267  				m.Get("/editorconfig/{filename}", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetEditorconfig)
  1268  				m.Group("/pulls", func() {
  1269  					m.Combo("").Get(repo.ListPullRequests).
  1270  						Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
  1271  					m.Get("/pinned", repo.ListPinnedPullRequests)
  1272  					m.Group("/{index}", func() {
  1273  						m.Combo("").Get(repo.GetPullRequest).
  1274  							Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
  1275  						m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch)
  1276  						m.Post("/update", reqToken(), repo.UpdatePullRequest)
  1277  						m.Get("/commits", repo.GetPullRequestCommits)
  1278  						m.Get("/files", repo.GetPullRequestFiles)
  1279  						m.Combo("/merge").Get(repo.IsPullRequestMerged).
  1280  							Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest).
  1281  							Delete(reqToken(), mustNotBeArchived, repo.CancelScheduledAutoMerge)
  1282  						m.Group("/reviews", func() {
  1283  							m.Combo("").
  1284  								Get(repo.ListPullReviews).
  1285  								Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)
  1286  							m.Group("/{id}", func() {
  1287  								m.Combo("").
  1288  									Get(repo.GetPullReview).
  1289  									Delete(reqToken(), repo.DeletePullReview).
  1290  									Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)
  1291  								m.Combo("/comments").
  1292  									Get(repo.GetPullReviewComments)
  1293  								m.Post("/dismissals", reqToken(), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview)
  1294  								m.Post("/undismissals", reqToken(), repo.UnDismissPullReview)
  1295  							})
  1296  						})
  1297  						m.Combo("/requested_reviewers", reqToken()).
  1298  							Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
  1299  							Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
  1300  					})
  1301  					m.Get("/{base}/*", repo.GetPullRequestByBaseHead)
  1302  				}, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
  1303  				m.Group("/statuses", func() {
  1304  					m.Combo("/{sha}").Get(repo.GetCommitStatuses).
  1305  						Post(reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
  1306  				}, reqRepoReader(unit.TypeCode))
  1307  				m.Group("/commits", func() {
  1308  					m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits)
  1309  					m.Group("/{ref}", func() {
  1310  						m.Get("/status", repo.GetCombinedCommitStatusByRef)
  1311  						m.Get("/statuses", repo.GetCommitStatusesByRef)
  1312  					}, context.ReferencesGitRepo())
  1313  					m.Group("/{sha}", func() {
  1314  						m.Get("/pull", repo.GetCommitPullRequest)
  1315  					}, context.ReferencesGitRepo())
  1316  				}, reqRepoReader(unit.TypeCode))
  1317  				m.Group("/git", func() {
  1318  					m.Group("/commits", func() {
  1319  						m.Get("/{sha}", repo.GetSingleCommit)
  1320  						m.Get("/{sha}.{diffType:diff|patch}", repo.DownloadCommitDiffOrPatch)
  1321  					})
  1322  					m.Get("/refs", repo.GetGitAllRefs)
  1323  					m.Get("/refs/*", repo.GetGitRefs)
  1324  					m.Get("/trees/{sha}", repo.GetTree)
  1325  					m.Get("/blobs/{sha}", repo.GetBlob)
  1326  					m.Get("/tags/{sha}", repo.GetAnnotatedTag)
  1327  					m.Get("/notes/{sha}", repo.GetNote)
  1328  				}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
  1329  				m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
  1330  				m.Group("/contents", func() {
  1331  					m.Get("", repo.GetContentsList)
  1332  					m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
  1333  					m.Get("/*", repo.GetContents)
  1334  					m.Group("/*", func() {
  1335  						m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
  1336  						m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
  1337  						m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
  1338  					}, reqToken())
  1339  				}, reqRepoReader(unit.TypeCode))
  1340  				m.Get("/signing-key.gpg", misc.SigningKey)
  1341  				m.Group("/topics", func() {
  1342  					m.Combo("").Get(repo.ListTopics).
  1343  						Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
  1344  					m.Group("/{topic}", func() {
  1345  						m.Combo("").Put(reqToken(), repo.AddTopic).
  1346  							Delete(reqToken(), repo.DeleteTopic)
  1347  					}, reqAdmin())
  1348  				}, reqAnyRepoReader())
  1349  				m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates)
  1350  				m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
  1351  				m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig)
  1352  				m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
  1353  				m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
  1354  				m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
  1355  				m.Group("/avatar", func() {
  1356  					m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
  1357  					m.Delete("", repo.DeleteAvatar)
  1358  				}, reqAdmin(), reqToken())
  1359  			}, repoAssignment(), checkTokenPublicOnly())
  1360  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
  1361  
  1362  		// Notifications (requires notifications scope)
  1363  		m.Group("/repos", func() {
  1364  			m.Group("/{username}/{reponame}", func() {
  1365  				m.Combo("/notifications", reqToken()).
  1366  					Get(notify.ListRepoNotifications).
  1367  					Put(notify.ReadRepoNotifications)
  1368  			}, repoAssignment(), checkTokenPublicOnly())
  1369  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
  1370  
  1371  		// Issue (requires issue scope)
  1372  		m.Group("/repos", func() {
  1373  			m.Get("/issues/search", repo.SearchIssues)
  1374  
  1375  			m.Group("/{username}/{reponame}", func() {
  1376  				m.Group("/issues", func() {
  1377  					m.Combo("").Get(repo.ListIssues).
  1378  						Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), reqRepoReader(unit.TypeIssues), repo.CreateIssue)
  1379  					m.Get("/pinned", reqRepoReader(unit.TypeIssues), repo.ListPinnedIssues)
  1380  					m.Group("/comments", func() {
  1381  						m.Get("", repo.ListRepoIssueComments)
  1382  						m.Group("/{id}", func() {
  1383  							m.Combo("").
  1384  								Get(repo.GetIssueComment).
  1385  								Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
  1386  								Delete(reqToken(), repo.DeleteIssueComment)
  1387  							m.Combo("/reactions").
  1388  								Get(repo.GetIssueCommentReactions).
  1389  								Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
  1390  								Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
  1391  							m.Group("/assets", func() {
  1392  								m.Combo("").
  1393  									Get(repo.ListIssueCommentAttachments).
  1394  									Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment)
  1395  								m.Combo("/{attachment_id}").
  1396  									Get(repo.GetIssueCommentAttachment).
  1397  									Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
  1398  									Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment)
  1399  							}, mustEnableAttachments)
  1400  						})
  1401  					})
  1402  					m.Group("/{index}", func() {
  1403  						m.Combo("").Get(repo.GetIssue).
  1404  							Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
  1405  							Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
  1406  						m.Group("/comments", func() {
  1407  							m.Combo("").Get(repo.ListIssueComments).
  1408  								Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
  1409  							m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
  1410  								Delete(repo.DeleteIssueCommentDeprecated)
  1411  						})
  1412  						m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
  1413  						m.Group("/labels", func() {
  1414  							m.Combo("").Get(repo.ListIssueLabels).
  1415  								Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
  1416  								Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
  1417  								Delete(reqToken(), repo.ClearIssueLabels)
  1418  							m.Delete("/{id}", reqToken(), repo.DeleteIssueLabel)
  1419  						})
  1420  						m.Group("/times", func() {
  1421  							m.Combo("").
  1422  								Get(repo.ListTrackedTimes).
  1423  								Post(bind(api.AddTimeOption{}), repo.AddTime).
  1424  								Delete(repo.ResetIssueTime)
  1425  							m.Delete("/{id}", repo.DeleteTime)
  1426  						}, reqToken())
  1427  						m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
  1428  						m.Group("/stopwatch", func() {
  1429  							m.Post("/start", repo.StartIssueStopwatch)
  1430  							m.Post("/stop", repo.StopIssueStopwatch)
  1431  							m.Delete("/delete", repo.DeleteIssueStopwatch)
  1432  						}, reqToken())
  1433  						m.Group("/subscriptions", func() {
  1434  							m.Get("", repo.GetIssueSubscribers)
  1435  							m.Get("/check", reqToken(), repo.CheckIssueSubscription)
  1436  							m.Put("/{user}", reqToken(), repo.AddIssueSubscription)
  1437  							m.Delete("/{user}", reqToken(), repo.DelIssueSubscription)
  1438  						})
  1439  						m.Combo("/reactions").
  1440  							Get(repo.GetIssueReactions).
  1441  							Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueReaction).
  1442  							Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
  1443  						m.Group("/assets", func() {
  1444  							m.Combo("").
  1445  								Get(repo.ListIssueAttachments).
  1446  								Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment)
  1447  							m.Combo("/{attachment_id}").
  1448  								Get(repo.GetIssueAttachment).
  1449  								Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment).
  1450  								Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueAttachment)
  1451  						}, mustEnableAttachments)
  1452  						m.Combo("/dependencies").
  1453  							Get(repo.GetIssueDependencies).
  1454  							Post(reqToken(), mustNotBeArchived, bind(api.IssueMeta{}), repo.CreateIssueDependency).
  1455  							Delete(reqToken(), mustNotBeArchived, bind(api.IssueMeta{}), repo.RemoveIssueDependency)
  1456  						m.Combo("/blocks").
  1457  							Get(repo.GetIssueBlocks).
  1458  							Post(reqToken(), bind(api.IssueMeta{}), repo.CreateIssueBlocking).
  1459  							Delete(reqToken(), bind(api.IssueMeta{}), repo.RemoveIssueBlocking)
  1460  						m.Group("/pin", func() {
  1461  							m.Combo("").
  1462  								Post(reqToken(), reqAdmin(), repo.PinIssue).
  1463  								Delete(reqToken(), reqAdmin(), repo.UnpinIssue)
  1464  							m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin)
  1465  						})
  1466  					})
  1467  				}, mustEnableIssuesOrPulls)
  1468  				m.Group("/labels", func() {
  1469  					m.Combo("").Get(repo.ListLabels).
  1470  						Post(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel)
  1471  					m.Combo("/{id}").Get(repo.GetLabel).
  1472  						Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel).
  1473  						Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteLabel)
  1474  				})
  1475  				m.Group("/milestones", func() {
  1476  					m.Combo("").Get(repo.ListMilestones).
  1477  						Post(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
  1478  					m.Combo("/{id}").Get(repo.GetMilestone).
  1479  						Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
  1480  						Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
  1481  				})
  1482  			}, repoAssignment(), checkTokenPublicOnly())
  1483  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
  1484  
  1485  		// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
  1486  		m.Group("/packages/{username}", func() {
  1487  			m.Group("/{type}/{name}/{version}", func() {
  1488  				m.Get("", reqToken(), packages.GetPackage)
  1489  				m.Delete("", reqToken(), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
  1490  				m.Get("/files", reqToken(), packages.ListPackageFiles)
  1491  			})
  1492  			m.Get("/", reqToken(), packages.ListPackages)
  1493  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
  1494  
  1495  		// Organizations
  1496  		m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
  1497  		m.Group("/users/{username}/orgs", func() {
  1498  			m.Get("", reqToken(), org.ListUserOrgs)
  1499  			m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
  1500  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly())
  1501  		m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
  1502  		m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
  1503  		m.Group("/orgs/{org}", func() {
  1504  			m.Combo("").Get(org.Get).
  1505  				Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
  1506  				Delete(reqToken(), reqOrgOwnership(), org.Delete)
  1507  			m.Combo("/repos").Get(user.ListOrgRepos).
  1508  				Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
  1509  			m.Group("/members", func() {
  1510  				m.Get("", reqToken(), org.ListMembers)
  1511  				m.Combo("/{username}").Get(reqToken(), org.IsMember).
  1512  					Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
  1513  			})
  1514  			addActionsRoutes(
  1515  				m,
  1516  				reqOrgOwnership(),
  1517  				org.NewAction(),
  1518  			)
  1519  			m.Group("/public_members", func() {
  1520  				m.Get("", org.ListPublicMembers)
  1521  				m.Combo("/{username}").Get(org.IsPublicMember).
  1522  					Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
  1523  					Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
  1524  			})
  1525  			m.Group("/teams", func() {
  1526  				m.Get("", org.ListTeams)
  1527  				m.Post("", reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
  1528  				m.Get("/search", org.SearchTeam)
  1529  			}, reqToken(), reqOrgMembership())
  1530  			m.Group("/labels", func() {
  1531  				m.Get("", org.ListLabels)
  1532  				m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
  1533  				m.Combo("/{id}").Get(reqToken(), org.GetLabel).
  1534  					Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
  1535  					Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
  1536  			})
  1537  			m.Group("/hooks", func() {
  1538  				m.Combo("").Get(org.ListHooks).
  1539  					Post(bind(api.CreateHookOption{}), org.CreateHook)
  1540  				m.Combo("/{id}").Get(org.GetHook).
  1541  					Patch(bind(api.EditHookOption{}), org.EditHook).
  1542  					Delete(org.DeleteHook)
  1543  			}, reqToken(), reqOrgOwnership(), reqWebhooksEnabled())
  1544  			m.Group("/avatar", func() {
  1545  				m.Post("", bind(api.UpdateUserAvatarOption{}), org.UpdateAvatar)
  1546  				m.Delete("", org.DeleteAvatar)
  1547  			}, reqToken(), reqOrgOwnership())
  1548  			m.Get("/activities/feeds", org.ListOrgActivityFeeds)
  1549  
  1550  			m.Group("/blocks", func() {
  1551  				m.Get("", org.ListBlocks)
  1552  				m.Group("/{username}", func() {
  1553  					m.Get("", org.CheckUserBlock)
  1554  					m.Put("", org.BlockUser)
  1555  					m.Delete("", org.UnblockUser)
  1556  				})
  1557  			}, reqToken(), reqOrgOwnership())
  1558  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
  1559  		m.Group("/teams/{teamid}", func() {
  1560  			m.Combo("").Get(reqToken(), org.GetTeam).
  1561  				Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
  1562  				Delete(reqToken(), reqOrgOwnership(), org.DeleteTeam)
  1563  			m.Group("/members", func() {
  1564  				m.Get("", reqToken(), org.GetTeamMembers)
  1565  				m.Combo("/{username}").
  1566  					Get(reqToken(), org.GetTeamMember).
  1567  					Put(reqToken(), reqOrgOwnership(), org.AddTeamMember).
  1568  					Delete(reqToken(), reqOrgOwnership(), org.RemoveTeamMember)
  1569  			})
  1570  			m.Group("/repos", func() {
  1571  				m.Get("", reqToken(), org.GetTeamRepos)
  1572  				m.Combo("/{org}/{reponame}").
  1573  					Put(reqToken(), org.AddTeamRepository).
  1574  					Delete(reqToken(), org.RemoveTeamRepository).
  1575  					Get(reqToken(), org.GetTeamRepo)
  1576  			})
  1577  			m.Get("/activities/feeds", org.ListTeamActivityFeeds)
  1578  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
  1579  
  1580  		m.Group("/admin", func() {
  1581  			m.Group("/cron", func() {
  1582  				m.Get("", admin.ListCronTasks)
  1583  				m.Post("/{task}", admin.PostCronTask)
  1584  			})
  1585  			m.Get("/orgs", admin.GetAllOrgs)
  1586  			m.Group("/users", func() {
  1587  				m.Get("", admin.SearchUsers)
  1588  				m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
  1589  				m.Group("/{username}", func() {
  1590  					m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
  1591  						Delete(admin.DeleteUser)
  1592  					m.Group("/keys", func() {
  1593  						m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
  1594  						m.Delete("/{id}", admin.DeleteUserPublicKey)
  1595  					})
  1596  					m.Get("/orgs", org.ListUserOrgs)
  1597  					m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
  1598  					m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
  1599  					m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
  1600  					m.Get("/badges", admin.ListUserBadges)
  1601  					m.Post("/badges", bind(api.UserBadgeOption{}), admin.AddUserBadges)
  1602  					m.Delete("/badges", bind(api.UserBadgeOption{}), admin.DeleteUserBadges)
  1603  				}, context.UserAssignmentAPI())
  1604  			})
  1605  			m.Group("/emails", func() {
  1606  				m.Get("", admin.GetAllEmails)
  1607  				m.Get("/search", admin.SearchEmail)
  1608  			})
  1609  			m.Group("/unadopted", func() {
  1610  				m.Get("", admin.ListUnadoptedRepositories)
  1611  				m.Post("/{username}/{reponame}", admin.AdoptRepository)
  1612  				m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository)
  1613  			})
  1614  			m.Group("/hooks", func() {
  1615  				m.Combo("").Get(admin.ListHooks).
  1616  					Post(bind(api.CreateHookOption{}), admin.CreateHook)
  1617  				m.Combo("/{id}").Get(admin.GetHook).
  1618  					Patch(bind(api.EditHookOption{}), admin.EditHook).
  1619  					Delete(admin.DeleteHook)
  1620  			})
  1621  			m.Group("/runners", func() {
  1622  				m.Get("/registration-token", admin.GetRegistrationToken)
  1623  			})
  1624  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
  1625  
  1626  		m.Group("/topics", func() {
  1627  			m.Get("/search", repo.TopicSearch)
  1628  		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
  1629  	}, sudo())
  1630  
  1631  	return m
  1632  }
  1633  
  1634  func securityHeaders() func(http.Handler) http.Handler {
  1635  	return func(next http.Handler) http.Handler {
  1636  		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  1637  			// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
  1638  			// http://stackoverflow.com/a/3146618/244009
  1639  			resp.Header().Set("x-content-type-options", "nosniff")
  1640  			next.ServeHTTP(resp, req)
  1641  		})
  1642  	}
  1643  }