gitea源码

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