You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

api.go 51KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  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: http, https
  9. // BasePath: /api/v1
  10. // Version: {{AppVer | JSEscape | Safe}}
  11. // License: MIT http://opensource.org/licenses/MIT
  12. //
  13. // Consumes:
  14. // - application/json
  15. // - text/plain
  16. //
  17. // Produces:
  18. // - application/json
  19. // - text/html
  20. //
  21. // Security:
  22. // - BasicAuth :
  23. // - Token :
  24. // - AccessToken :
  25. // - AuthorizationHeaderToken :
  26. // - SudoParam :
  27. // - SudoHeader :
  28. // - TOTPHeader :
  29. //
  30. // SecurityDefinitions:
  31. // BasicAuth:
  32. // type: basic
  33. // Token:
  34. // type: apiKey
  35. // name: token
  36. // in: query
  37. // AccessToken:
  38. // type: apiKey
  39. // name: access_token
  40. // in: query
  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. "fmt"
  67. "net/http"
  68. "strings"
  69. auth_model "code.gitea.io/gitea/models/auth"
  70. "code.gitea.io/gitea/models/organization"
  71. "code.gitea.io/gitea/models/perm"
  72. access_model "code.gitea.io/gitea/models/perm/access"
  73. repo_model "code.gitea.io/gitea/models/repo"
  74. "code.gitea.io/gitea/models/unit"
  75. user_model "code.gitea.io/gitea/models/user"
  76. "code.gitea.io/gitea/modules/context"
  77. "code.gitea.io/gitea/modules/log"
  78. "code.gitea.io/gitea/modules/setting"
  79. api "code.gitea.io/gitea/modules/structs"
  80. "code.gitea.io/gitea/modules/web"
  81. "code.gitea.io/gitea/routers/api/v1/activitypub"
  82. "code.gitea.io/gitea/routers/api/v1/admin"
  83. "code.gitea.io/gitea/routers/api/v1/misc"
  84. "code.gitea.io/gitea/routers/api/v1/notify"
  85. "code.gitea.io/gitea/routers/api/v1/org"
  86. "code.gitea.io/gitea/routers/api/v1/packages"
  87. "code.gitea.io/gitea/routers/api/v1/repo"
  88. "code.gitea.io/gitea/routers/api/v1/settings"
  89. "code.gitea.io/gitea/routers/api/v1/user"
  90. "code.gitea.io/gitea/services/auth"
  91. context_service "code.gitea.io/gitea/services/context"
  92. "code.gitea.io/gitea/services/forms"
  93. _ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
  94. "gitea.com/go-chi/binding"
  95. "github.com/go-chi/cors"
  96. )
  97. func sudo() func(ctx *context.APIContext) {
  98. return func(ctx *context.APIContext) {
  99. sudo := ctx.FormString("sudo")
  100. if len(sudo) == 0 {
  101. sudo = ctx.Req.Header.Get("Sudo")
  102. }
  103. if len(sudo) > 0 {
  104. if ctx.IsSigned && ctx.Doer.IsAdmin {
  105. user, err := user_model.GetUserByName(ctx, sudo)
  106. if err != nil {
  107. if user_model.IsErrUserNotExist(err) {
  108. ctx.NotFound()
  109. } else {
  110. ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
  111. }
  112. return
  113. }
  114. log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name)
  115. ctx.Doer = user
  116. } else {
  117. ctx.JSON(http.StatusForbidden, map[string]string{
  118. "message": "Only administrators allowed to sudo.",
  119. })
  120. return
  121. }
  122. }
  123. }
  124. }
  125. func repoAssignment() func(ctx *context.APIContext) {
  126. return func(ctx *context.APIContext) {
  127. userName := ctx.Params("username")
  128. repoName := ctx.Params("reponame")
  129. var (
  130. owner *user_model.User
  131. err error
  132. )
  133. // Check if the user is the same as the repository owner.
  134. if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
  135. owner = ctx.Doer
  136. } else {
  137. owner, err = user_model.GetUserByName(ctx, userName)
  138. if err != nil {
  139. if user_model.IsErrUserNotExist(err) {
  140. if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil {
  141. context.RedirectToUser(ctx.Context, userName, redirectUserID)
  142. } else if user_model.IsErrUserRedirectNotExist(err) {
  143. ctx.NotFound("GetUserByName", err)
  144. } else {
  145. ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
  146. }
  147. } else {
  148. ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
  149. }
  150. return
  151. }
  152. }
  153. ctx.Repo.Owner = owner
  154. ctx.ContextUser = owner
  155. // Get repository.
  156. repo, err := repo_model.GetRepositoryByName(owner.ID, repoName)
  157. if err != nil {
  158. if repo_model.IsErrRepoNotExist(err) {
  159. redirectRepoID, err := repo_model.LookupRedirect(owner.ID, repoName)
  160. if err == nil {
  161. context.RedirectToRepo(ctx.Context, redirectRepoID)
  162. } else if repo_model.IsErrRedirectNotExist(err) {
  163. ctx.NotFound()
  164. } else {
  165. ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err)
  166. }
  167. } else {
  168. ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
  169. }
  170. return
  171. }
  172. repo.Owner = owner
  173. ctx.Repo.Repository = repo
  174. ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  175. if err != nil {
  176. ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
  177. return
  178. }
  179. if !ctx.Repo.HasAccess() {
  180. ctx.NotFound()
  181. return
  182. }
  183. }
  184. }
  185. func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
  186. return func(ctx *context.APIContext) {
  187. if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
  188. ctx.Error(http.StatusForbidden, "reqPackageAccess", "user should have specific permission or be a site admin")
  189. return
  190. }
  191. }
  192. }
  193. // Contexter middleware already checks token for user sign in process.
  194. func reqToken(requiredScope auth_model.AccessTokenScope) func(ctx *context.APIContext) {
  195. return func(ctx *context.APIContext) {
  196. // If OAuth2 token is present
  197. if _, ok := ctx.Data["ApiTokenScope"]; ctx.Data["IsApiToken"] == true && ok {
  198. // no scope required
  199. if requiredScope == "" {
  200. return
  201. }
  202. // check scope
  203. scope := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
  204. allow, err := scope.HasScope(requiredScope)
  205. if err != nil {
  206. ctx.Error(http.StatusForbidden, "reqToken", "parsing token failed: "+err.Error())
  207. return
  208. }
  209. if allow {
  210. return
  211. }
  212. // if requires 'repo' scope, but only has 'public_repo' scope, allow it only if the repo is public
  213. if requiredScope == auth_model.AccessTokenScopeRepo {
  214. if allowPublicRepo, err := scope.HasScope(auth_model.AccessTokenScopePublicRepo); err == nil && allowPublicRepo {
  215. if ctx.Repo.Repository != nil && !ctx.Repo.Repository.IsPrivate {
  216. return
  217. }
  218. }
  219. }
  220. ctx.Error(http.StatusForbidden, "reqToken", "token does not have required scope: "+requiredScope)
  221. return
  222. }
  223. if ctx.Context.IsBasicAuth {
  224. ctx.CheckForOTP()
  225. return
  226. }
  227. if ctx.IsSigned {
  228. return
  229. }
  230. ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
  231. }
  232. }
  233. func reqExploreSignIn() func(ctx *context.APIContext) {
  234. return func(ctx *context.APIContext) {
  235. if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
  236. ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
  237. }
  238. }
  239. }
  240. func reqBasicAuth() func(ctx *context.APIContext) {
  241. return func(ctx *context.APIContext) {
  242. if !ctx.Context.IsBasicAuth {
  243. ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
  244. return
  245. }
  246. ctx.CheckForOTP()
  247. }
  248. }
  249. // reqSiteAdmin user should be the site admin
  250. func reqSiteAdmin() func(ctx *context.APIContext) {
  251. return func(ctx *context.APIContext) {
  252. if !ctx.IsUserSiteAdmin() {
  253. ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
  254. return
  255. }
  256. }
  257. }
  258. // reqOwner user should be the owner of the repo or site admin.
  259. func reqOwner() func(ctx *context.APIContext) {
  260. return func(ctx *context.APIContext) {
  261. if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() {
  262. ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
  263. return
  264. }
  265. }
  266. }
  267. // reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin
  268. func reqAdmin() func(ctx *context.APIContext) {
  269. return func(ctx *context.APIContext) {
  270. if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  271. ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
  272. return
  273. }
  274. }
  275. }
  276. // reqRepoWriter user should have a permission to write to a repo, or be a site admin
  277. func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
  278. return func(ctx *context.APIContext) {
  279. if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  280. ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
  281. return
  282. }
  283. }
  284. }
  285. // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
  286. func reqRepoBranchWriter(ctx *context.APIContext) {
  287. options, ok := web.GetForm(ctx).(api.FileOptionInterface)
  288. if !ok || (!ctx.Repo.CanWriteToBranch(ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
  289. ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch")
  290. return
  291. }
  292. }
  293. // reqRepoReader user should have specific read permission or be a repo admin or a site admin
  294. func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
  295. return func(ctx *context.APIContext) {
  296. if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  297. ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
  298. return
  299. }
  300. }
  301. }
  302. // reqAnyRepoReader user should have any permission to read repository or permissions of site admin
  303. func reqAnyRepoReader() func(ctx *context.APIContext) {
  304. return func(ctx *context.APIContext) {
  305. if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() {
  306. ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
  307. return
  308. }
  309. }
  310. }
  311. // reqOrgOwnership user should be an organization owner, or a site admin
  312. func reqOrgOwnership() func(ctx *context.APIContext) {
  313. return func(ctx *context.APIContext) {
  314. if ctx.Context.IsUserSiteAdmin() {
  315. return
  316. }
  317. var orgID int64
  318. if ctx.Org.Organization != nil {
  319. orgID = ctx.Org.Organization.ID
  320. } else if ctx.Org.Team != nil {
  321. orgID = ctx.Org.Team.OrgID
  322. } else {
  323. ctx.Error(http.StatusInternalServerError, "", "reqOrgOwnership: unprepared context")
  324. return
  325. }
  326. isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
  327. if err != nil {
  328. ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
  329. return
  330. } else if !isOwner {
  331. if ctx.Org.Organization != nil {
  332. ctx.Error(http.StatusForbidden, "", "Must be an organization owner")
  333. } else {
  334. ctx.NotFound()
  335. }
  336. return
  337. }
  338. }
  339. }
  340. // reqTeamMembership user should be an team member, or a site admin
  341. func reqTeamMembership() func(ctx *context.APIContext) {
  342. return func(ctx *context.APIContext) {
  343. if ctx.Context.IsUserSiteAdmin() {
  344. return
  345. }
  346. if ctx.Org.Team == nil {
  347. ctx.Error(http.StatusInternalServerError, "", "reqTeamMembership: unprepared context")
  348. return
  349. }
  350. orgID := ctx.Org.Team.OrgID
  351. isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
  352. if err != nil {
  353. ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
  354. return
  355. } else if isOwner {
  356. return
  357. }
  358. if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil {
  359. ctx.Error(http.StatusInternalServerError, "IsTeamMember", err)
  360. return
  361. } else if !isTeamMember {
  362. isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
  363. if err != nil {
  364. ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
  365. } else if isOrgMember {
  366. ctx.Error(http.StatusForbidden, "", "Must be a team member")
  367. } else {
  368. ctx.NotFound()
  369. }
  370. return
  371. }
  372. }
  373. }
  374. // reqOrgMembership user should be an organization member, or a site admin
  375. func reqOrgMembership() func(ctx *context.APIContext) {
  376. return func(ctx *context.APIContext) {
  377. if ctx.Context.IsUserSiteAdmin() {
  378. return
  379. }
  380. var orgID int64
  381. if ctx.Org.Organization != nil {
  382. orgID = ctx.Org.Organization.ID
  383. } else if ctx.Org.Team != nil {
  384. orgID = ctx.Org.Team.OrgID
  385. } else {
  386. ctx.Error(http.StatusInternalServerError, "", "reqOrgMembership: unprepared context")
  387. return
  388. }
  389. if isMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID); err != nil {
  390. ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
  391. return
  392. } else if !isMember {
  393. if ctx.Org.Organization != nil {
  394. ctx.Error(http.StatusForbidden, "", "Must be an organization member")
  395. } else {
  396. ctx.NotFound()
  397. }
  398. return
  399. }
  400. }
  401. }
  402. func reqGitHook() func(ctx *context.APIContext) {
  403. return func(ctx *context.APIContext) {
  404. if !ctx.Doer.CanEditGitHook() {
  405. ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
  406. return
  407. }
  408. }
  409. }
  410. // reqWebhooksEnabled requires webhooks to be enabled by admin.
  411. func reqWebhooksEnabled() func(ctx *context.APIContext) {
  412. return func(ctx *context.APIContext) {
  413. if setting.DisableWebhooks {
  414. ctx.Error(http.StatusForbidden, "", "webhooks disabled by administrator")
  415. return
  416. }
  417. }
  418. }
  419. func orgAssignment(args ...bool) func(ctx *context.APIContext) {
  420. var (
  421. assignOrg bool
  422. assignTeam bool
  423. )
  424. if len(args) > 0 {
  425. assignOrg = args[0]
  426. }
  427. if len(args) > 1 {
  428. assignTeam = args[1]
  429. }
  430. return func(ctx *context.APIContext) {
  431. ctx.Org = new(context.APIOrganization)
  432. var err error
  433. if assignOrg {
  434. ctx.Org.Organization, err = organization.GetOrgByName(ctx.Params(":org"))
  435. if err != nil {
  436. if organization.IsErrOrgNotExist(err) {
  437. redirectUserID, err := user_model.LookupUserRedirect(ctx.Params(":org"))
  438. if err == nil {
  439. context.RedirectToUser(ctx.Context, ctx.Params(":org"), redirectUserID)
  440. } else if user_model.IsErrUserRedirectNotExist(err) {
  441. ctx.NotFound("GetOrgByName", err)
  442. } else {
  443. ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
  444. }
  445. } else {
  446. ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
  447. }
  448. return
  449. }
  450. ctx.ContextUser = ctx.Org.Organization.AsUser()
  451. }
  452. if assignTeam {
  453. ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.ParamsInt64(":teamid"))
  454. if err != nil {
  455. if organization.IsErrTeamNotExist(err) {
  456. ctx.NotFound()
  457. } else {
  458. ctx.Error(http.StatusInternalServerError, "GetTeamById", err)
  459. }
  460. return
  461. }
  462. }
  463. }
  464. }
  465. func mustEnableIssues(ctx *context.APIContext) {
  466. if !ctx.Repo.CanRead(unit.TypeIssues) {
  467. if log.IsTrace() {
  468. if ctx.IsSigned {
  469. log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
  470. "User in Repo has Permissions: %-+v",
  471. ctx.Doer,
  472. unit.TypeIssues,
  473. ctx.Repo.Repository,
  474. ctx.Repo.Permission)
  475. } else {
  476. log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
  477. "Anonymous user in Repo has Permissions: %-+v",
  478. unit.TypeIssues,
  479. ctx.Repo.Repository,
  480. ctx.Repo.Permission)
  481. }
  482. }
  483. ctx.NotFound()
  484. return
  485. }
  486. }
  487. func mustAllowPulls(ctx *context.APIContext) {
  488. if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
  489. if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
  490. if ctx.IsSigned {
  491. log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
  492. "User in Repo has Permissions: %-+v",
  493. ctx.Doer,
  494. unit.TypePullRequests,
  495. ctx.Repo.Repository,
  496. ctx.Repo.Permission)
  497. } else {
  498. log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
  499. "Anonymous user in Repo has Permissions: %-+v",
  500. unit.TypePullRequests,
  501. ctx.Repo.Repository,
  502. ctx.Repo.Permission)
  503. }
  504. }
  505. ctx.NotFound()
  506. return
  507. }
  508. }
  509. func mustEnableIssuesOrPulls(ctx *context.APIContext) {
  510. if !ctx.Repo.CanRead(unit.TypeIssues) &&
  511. !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
  512. if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
  513. if ctx.IsSigned {
  514. log.Trace("Permission Denied: User %-v cannot read %-v and %-v in Repo %-v\n"+
  515. "User in Repo has Permissions: %-+v",
  516. ctx.Doer,
  517. unit.TypeIssues,
  518. unit.TypePullRequests,
  519. ctx.Repo.Repository,
  520. ctx.Repo.Permission)
  521. } else {
  522. log.Trace("Permission Denied: Anonymous user cannot read %-v and %-v in Repo %-v\n"+
  523. "Anonymous user in Repo has Permissions: %-+v",
  524. unit.TypeIssues,
  525. unit.TypePullRequests,
  526. ctx.Repo.Repository,
  527. ctx.Repo.Permission)
  528. }
  529. }
  530. ctx.NotFound()
  531. return
  532. }
  533. }
  534. func mustEnableWiki(ctx *context.APIContext) {
  535. if !(ctx.Repo.CanRead(unit.TypeWiki)) {
  536. ctx.NotFound()
  537. return
  538. }
  539. }
  540. func mustNotBeArchived(ctx *context.APIContext) {
  541. if ctx.Repo.Repository.IsArchived {
  542. ctx.NotFound()
  543. return
  544. }
  545. }
  546. func mustEnableAttachments(ctx *context.APIContext) {
  547. if !setting.Attachment.Enabled {
  548. ctx.NotFound()
  549. return
  550. }
  551. }
  552. // bind binding an obj to a func(ctx *context.APIContext)
  553. func bind[T any](obj T) http.HandlerFunc {
  554. return web.Wrap(func(ctx *context.APIContext) {
  555. theObj := new(T) // create a new form obj for every request but not use obj directly
  556. errs := binding.Bind(ctx.Req, theObj)
  557. if len(errs) > 0 {
  558. ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
  559. return
  560. }
  561. web.SetForm(ctx, theObj)
  562. })
  563. }
  564. // The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
  565. // in the session (if there is a user id stored in session other plugins might return the user
  566. // object for that id).
  567. //
  568. // The Session plugin is expected to be executed second, in order to skip authentication
  569. // for users that have already signed in.
  570. func buildAuthGroup() *auth.Group {
  571. group := auth.NewGroup(
  572. &auth.OAuth2{},
  573. &auth.HTTPSign{},
  574. &auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
  575. )
  576. specialAdd(group)
  577. return group
  578. }
  579. // Routes registers all v1 APIs routes to web application.
  580. func Routes(ctx gocontext.Context) *web.Route {
  581. m := web.NewRoute()
  582. m.Use(securityHeaders())
  583. if setting.CORSConfig.Enabled {
  584. m.Use(cors.Handler(cors.Options{
  585. // Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
  586. AllowedOrigins: setting.CORSConfig.AllowDomain,
  587. // setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
  588. AllowedMethods: setting.CORSConfig.Methods,
  589. AllowCredentials: setting.CORSConfig.AllowCredentials,
  590. AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
  591. MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
  592. }))
  593. }
  594. m.Use(context.APIContexter())
  595. group := buildAuthGroup()
  596. if err := group.Init(ctx); err != nil {
  597. log.Error("Could not initialize '%s' auth method, error: %s", group.Name(), err)
  598. }
  599. // Get user from session if logged in.
  600. m.Use(context.APIAuth(group))
  601. m.Use(context.ToggleAPI(&context.ToggleOptions{
  602. SignInRequired: setting.Service.RequireSignInView,
  603. }))
  604. m.Group("", func() {
  605. // Miscellaneous (no scope required)
  606. if setting.API.EnableSwagger {
  607. m.Get("/swagger", func(ctx *context.APIContext) {
  608. ctx.Redirect(setting.AppSubURL + "/api/swagger")
  609. })
  610. }
  611. m.Get("/version", misc.Version)
  612. if setting.Federation.Enabled {
  613. m.Get("/nodeinfo", misc.NodeInfo)
  614. m.Group("/activitypub", func() {
  615. m.Group("/user/{username}", func() {
  616. m.Get("", activitypub.Person)
  617. m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
  618. }, context_service.UserAssignmentAPI())
  619. })
  620. }
  621. m.Get("/signing-key.gpg", misc.SigningKey)
  622. m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
  623. m.Post("/markdown/raw", misc.MarkdownRaw)
  624. m.Group("/settings", func() {
  625. m.Get("/ui", settings.GetGeneralUISettings)
  626. m.Get("/api", settings.GetGeneralAPISettings)
  627. m.Get("/attachment", settings.GetGeneralAttachmentSettings)
  628. m.Get("/repository", settings.GetGeneralRepoSettings)
  629. })
  630. // Notifications (requires 'notification' scope)
  631. m.Group("/notifications", func() {
  632. m.Combo("").
  633. Get(notify.ListNotifications).
  634. Put(notify.ReadNotifications)
  635. m.Get("/new", notify.NewAvailable)
  636. m.Combo("/threads/{id}").
  637. Get(notify.GetThread).
  638. Patch(notify.ReadThread)
  639. }, reqToken(auth_model.AccessTokenScopeNotification))
  640. // Users (no scope required)
  641. m.Group("/users", func() {
  642. m.Get("/search", reqExploreSignIn(), user.Search)
  643. m.Group("/{username}", func() {
  644. m.Get("", reqExploreSignIn(), user.GetInfo)
  645. if setting.Service.EnableUserHeatmap {
  646. m.Get("/heatmap", user.GetUserHeatmapData)
  647. }
  648. m.Get("/repos", reqExploreSignIn(), user.ListUserRepos)
  649. m.Group("/tokens", func() {
  650. m.Combo("").Get(user.ListAccessTokens).
  651. Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
  652. m.Combo("/{id}").Delete(user.DeleteAccessToken)
  653. }, reqBasicAuth())
  654. }, context_service.UserAssignmentAPI())
  655. })
  656. // (no scope required)
  657. m.Group("/users", func() {
  658. m.Group("/{username}", func() {
  659. m.Get("/keys", user.ListPublicKeys)
  660. m.Get("/gpg_keys", user.ListGPGKeys)
  661. m.Get("/followers", user.ListFollowers)
  662. m.Group("/following", func() {
  663. m.Get("", user.ListFollowing)
  664. m.Get("/{target}", user.CheckFollowing)
  665. })
  666. m.Get("/starred", user.GetStarredRepos)
  667. m.Get("/subscriptions", user.GetWatchedRepos)
  668. }, context_service.UserAssignmentAPI())
  669. }, reqToken(""))
  670. m.Group("/user", func() {
  671. m.Get("", user.GetAuthenticatedUser)
  672. m.Group("/settings", func() {
  673. m.Get("", reqToken(auth_model.AccessTokenScopeReadUser), user.GetUserSettings)
  674. m.Patch("", reqToken(auth_model.AccessTokenScopeUser), bind(api.UserSettingsOptions{}), user.UpdateUserSettings)
  675. })
  676. m.Combo("/emails").Get(reqToken(auth_model.AccessTokenScopeReadUser), user.ListEmails).
  677. Post(reqToken(auth_model.AccessTokenScopeUser), bind(api.CreateEmailOption{}), user.AddEmail).
  678. Delete(reqToken(auth_model.AccessTokenScopeUser), bind(api.DeleteEmailOption{}), user.DeleteEmail)
  679. m.Get("/followers", user.ListMyFollowers)
  680. m.Group("/following", func() {
  681. m.Get("", user.ListMyFollowing)
  682. m.Group("/{username}", func() {
  683. m.Get("", user.CheckMyFollowing)
  684. m.Put("", reqToken(auth_model.AccessTokenScopeUserFollow), user.Follow) // requires 'user:follow' scope
  685. m.Delete("", reqToken(auth_model.AccessTokenScopeUserFollow), user.Unfollow) // requires 'user:follow' scope
  686. }, context_service.UserAssignmentAPI())
  687. })
  688. // (admin:public_key scope)
  689. m.Group("/keys", func() {
  690. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadPublicKey), user.ListMyPublicKeys).
  691. Post(reqToken(auth_model.AccessTokenScopeWritePublicKey), bind(api.CreateKeyOption{}), user.CreatePublicKey)
  692. m.Combo("/{id}").Get(reqToken(auth_model.AccessTokenScopeReadPublicKey), user.GetPublicKey).
  693. Delete(reqToken(auth_model.AccessTokenScopeWritePublicKey), user.DeletePublicKey)
  694. })
  695. // (admin:application scope)
  696. m.Group("/applications", func() {
  697. m.Combo("/oauth2").
  698. Get(reqToken(auth_model.AccessTokenScopeReadApplication), user.ListOauth2Applications).
  699. Post(reqToken(auth_model.AccessTokenScopeWriteApplication), bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application)
  700. m.Combo("/oauth2/{id}").
  701. Delete(reqToken(auth_model.AccessTokenScopeWriteApplication), user.DeleteOauth2Application).
  702. Patch(reqToken(auth_model.AccessTokenScopeWriteApplication), bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
  703. Get(reqToken(auth_model.AccessTokenScopeReadApplication), user.GetOauth2Application)
  704. })
  705. // (admin:gpg_key scope)
  706. m.Group("/gpg_keys", func() {
  707. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadGPGKey), user.ListMyGPGKeys).
  708. Post(reqToken(auth_model.AccessTokenScopeWriteGPGKey), bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
  709. m.Combo("/{id}").Get(reqToken(auth_model.AccessTokenScopeReadGPGKey), user.GetGPGKey).
  710. Delete(reqToken(auth_model.AccessTokenScopeWriteGPGKey), user.DeleteGPGKey)
  711. })
  712. m.Get("/gpg_key_token", reqToken(auth_model.AccessTokenScopeReadGPGKey), user.GetVerificationToken)
  713. m.Post("/gpg_key_verify", reqToken(auth_model.AccessTokenScopeReadGPGKey), bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
  714. // (repo scope)
  715. m.Combo("/repos", reqToken(auth_model.AccessTokenScopeRepo)).Get(user.ListMyRepos).
  716. Post(bind(api.CreateRepoOption{}), repo.Create)
  717. // (repo scope)
  718. m.Group("/starred", func() {
  719. m.Get("", user.GetMyStarredRepos)
  720. m.Group("/{username}/{reponame}", func() {
  721. m.Get("", user.IsStarring)
  722. m.Put("", user.Star)
  723. m.Delete("", user.Unstar)
  724. }, repoAssignment())
  725. }, reqToken(auth_model.AccessTokenScopeRepo))
  726. m.Get("/times", reqToken(auth_model.AccessTokenScopeRepo), repo.ListMyTrackedTimes)
  727. m.Get("/stopwatches", reqToken(auth_model.AccessTokenScopeRepo), repo.GetStopwatches)
  728. m.Get("/subscriptions", reqToken(auth_model.AccessTokenScopeRepo), user.GetMyWatchedRepos)
  729. m.Get("/teams", reqToken(auth_model.AccessTokenScopeRepo), org.ListUserTeams)
  730. }, reqToken(""))
  731. // Repositories
  732. m.Post("/org/{org}/repos", reqToken(auth_model.AccessTokenScopeAdminOrg), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated)
  733. m.Combo("/repositories/{id}", reqToken(auth_model.AccessTokenScopeRepo)).Get(repo.GetByID)
  734. m.Group("/repos", func() {
  735. m.Get("/search", repo.Search)
  736. m.Get("/issues/search", repo.SearchIssues)
  737. // (repo scope)
  738. m.Post("/migrate", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MigrateRepoOptions{}), repo.Migrate)
  739. m.Group("/{username}/{reponame}", func() {
  740. m.Combo("").Get(reqAnyRepoReader(), repo.Get).
  741. Delete(reqToken(auth_model.AccessTokenScopeDeleteRepo), reqOwner(), repo.Delete).
  742. Patch(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
  743. m.Post("/generate", reqToken(auth_model.AccessTokenScopeRepo), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
  744. m.Group("/transfer", func() {
  745. m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
  746. m.Post("/accept", repo.AcceptTransfer)
  747. m.Post("/reject", repo.RejectTransfer)
  748. }, reqToken(auth_model.AccessTokenScopeRepo))
  749. m.Combo("/notifications", reqToken(auth_model.AccessTokenScopeNotification)).
  750. Get(notify.ListRepoNotifications).
  751. Put(notify.ReadRepoNotifications)
  752. m.Group("/hooks/git", func() {
  753. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.ListGitHooks)
  754. m.Group("/{id}", func() {
  755. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.GetGitHook).
  756. Patch(reqToken(auth_model.AccessTokenScopeWriteRepoHook), bind(api.EditGitHookOption{}), repo.EditGitHook).
  757. Delete(reqToken(auth_model.AccessTokenScopeWriteRepoHook), repo.DeleteGitHook)
  758. })
  759. }, reqAdmin(), reqGitHook(), context.ReferencesGitRepo(true))
  760. m.Group("/hooks", func() {
  761. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.ListHooks).
  762. Post(reqToken(auth_model.AccessTokenScopeWriteRepoHook), bind(api.CreateHookOption{}), repo.CreateHook)
  763. m.Group("/{id}", func() {
  764. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.GetHook).
  765. Patch(reqToken(auth_model.AccessTokenScopeWriteRepoHook), bind(api.EditHookOption{}), repo.EditHook).
  766. Delete(reqToken(auth_model.AccessTokenScopeWriteRepoHook), repo.DeleteHook)
  767. m.Post("/tests", reqToken(auth_model.AccessTokenScopeReadRepoHook), context.ReferencesGitRepo(), context.RepoRefForAPI, repo.TestHook)
  768. })
  769. }, reqAdmin(), reqWebhooksEnabled())
  770. m.Group("/collaborators", func() {
  771. m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
  772. m.Group("/{collaborator}", func() {
  773. m.Combo("").Get(reqAnyRepoReader(), repo.IsCollaborator).
  774. Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
  775. Delete(reqAdmin(), repo.DeleteCollaborator)
  776. m.Get("/permission", repo.GetRepoPermissions)
  777. })
  778. }, reqToken(auth_model.AccessTokenScopeRepo))
  779. m.Get("/assignees", reqToken(auth_model.AccessTokenScopeRepo), reqAnyRepoReader(), repo.GetAssignees)
  780. m.Get("/reviewers", reqToken(auth_model.AccessTokenScopeRepo), reqAnyRepoReader(), repo.GetReviewers)
  781. m.Group("/teams", func() {
  782. m.Get("", reqAnyRepoReader(), repo.ListTeams)
  783. m.Combo("/{team}").Get(reqAnyRepoReader(), repo.IsTeam).
  784. Put(reqAdmin(), repo.AddTeam).
  785. Delete(reqAdmin(), repo.DeleteTeam)
  786. }, reqToken(auth_model.AccessTokenScopeRepo))
  787. m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
  788. m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
  789. m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
  790. m.Combo("/forks").Get(repo.ListForks).
  791. Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
  792. m.Group("/branches", func() {
  793. m.Get("", repo.ListBranches)
  794. m.Get("/*", repo.GetBranch)
  795. m.Delete("/*", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), repo.DeleteBranch)
  796. m.Post("", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
  797. }, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
  798. m.Group("/branch_protections", func() {
  799. m.Get("", repo.ListBranchProtections)
  800. m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection)
  801. m.Group("/{name}", func() {
  802. m.Get("", repo.GetBranchProtection)
  803. m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection)
  804. m.Delete("", repo.DeleteBranchProtection)
  805. })
  806. }, reqToken(auth_model.AccessTokenScopeRepo), reqAdmin())
  807. m.Group("/tags", func() {
  808. m.Get("", repo.ListTags)
  809. m.Get("/*", repo.GetTag)
  810. m.Post("", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), bind(api.CreateTagOption{}), repo.CreateTag)
  811. m.Delete("/*", reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteTag)
  812. }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
  813. m.Group("/keys", func() {
  814. m.Combo("").Get(repo.ListDeployKeys).
  815. Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
  816. m.Combo("/{id}").Get(repo.GetDeployKey).
  817. Delete(repo.DeleteDeploykey)
  818. }, reqToken(auth_model.AccessTokenScopeRepo), reqAdmin())
  819. m.Group("/times", func() {
  820. m.Combo("").Get(repo.ListTrackedTimesByRepository)
  821. m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
  822. }, mustEnableIssues, reqToken(auth_model.AccessTokenScopeRepo))
  823. m.Group("/wiki", func() {
  824. m.Combo("/page/{pageName}").
  825. Get(repo.GetWikiPage).
  826. Patch(mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
  827. Delete(mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
  828. m.Get("/revisions/{pageName}", repo.ListPageRevisions)
  829. m.Post("/new", mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
  830. m.Get("/pages", repo.ListWikiPages)
  831. }, mustEnableWiki)
  832. m.Group("/issues", func() {
  833. m.Combo("").Get(repo.ListIssues).
  834. Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
  835. m.Group("/comments", func() {
  836. m.Get("", repo.ListRepoIssueComments)
  837. m.Group("/{id}", func() {
  838. m.Combo("").
  839. Get(repo.GetIssueComment).
  840. Patch(mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
  841. Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteIssueComment)
  842. m.Combo("/reactions").
  843. Get(repo.GetIssueCommentReactions).
  844. Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
  845. Delete(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
  846. m.Group("/assets", func() {
  847. m.Combo("").
  848. Get(repo.ListIssueCommentAttachments).
  849. Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.CreateIssueCommentAttachment)
  850. m.Combo("/{asset}").
  851. Get(repo.GetIssueCommentAttachment).
  852. Patch(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
  853. Delete(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.DeleteIssueCommentAttachment)
  854. }, mustEnableAttachments)
  855. })
  856. })
  857. m.Group("/{index}", func() {
  858. m.Combo("").Get(repo.GetIssue).
  859. Patch(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditIssueOption{}), repo.EditIssue).
  860. Delete(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
  861. m.Group("/comments", func() {
  862. m.Combo("").Get(repo.ListIssueComments).
  863. Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
  864. m.Combo("/{id}", reqToken(auth_model.AccessTokenScopeRepo)).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
  865. Delete(repo.DeleteIssueCommentDeprecated)
  866. })
  867. m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
  868. m.Group("/labels", func() {
  869. m.Combo("").Get(repo.ListIssueLabels).
  870. Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
  871. Put(reqToken(auth_model.AccessTokenScopeRepo), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
  872. Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.ClearIssueLabels)
  873. m.Delete("/{id}", reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteIssueLabel)
  874. })
  875. m.Group("/times", func() {
  876. m.Combo("").
  877. Get(repo.ListTrackedTimes).
  878. Post(bind(api.AddTimeOption{}), repo.AddTime).
  879. Delete(repo.ResetIssueTime)
  880. m.Delete("/{id}", repo.DeleteTime)
  881. }, reqToken(auth_model.AccessTokenScopeRepo))
  882. m.Combo("/deadline").Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
  883. m.Group("/stopwatch", func() {
  884. m.Post("/start", reqToken(auth_model.AccessTokenScopeRepo), repo.StartIssueStopwatch)
  885. m.Post("/stop", reqToken(auth_model.AccessTokenScopeRepo), repo.StopIssueStopwatch)
  886. m.Delete("/delete", reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteIssueStopwatch)
  887. })
  888. m.Group("/subscriptions", func() {
  889. m.Get("", repo.GetIssueSubscribers)
  890. m.Get("/check", reqToken(auth_model.AccessTokenScopeRepo), repo.CheckIssueSubscription)
  891. m.Put("/{user}", reqToken(auth_model.AccessTokenScopeRepo), repo.AddIssueSubscription)
  892. m.Delete("/{user}", reqToken(auth_model.AccessTokenScopeRepo), repo.DelIssueSubscription)
  893. })
  894. m.Combo("/reactions").
  895. Get(repo.GetIssueReactions).
  896. Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.PostIssueReaction).
  897. Delete(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
  898. m.Group("/assets", func() {
  899. m.Combo("").
  900. Get(repo.ListIssueAttachments).
  901. Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.CreateIssueAttachment)
  902. m.Combo("/{asset}").
  903. Get(repo.GetIssueAttachment).
  904. Patch(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment).
  905. Delete(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.DeleteIssueAttachment)
  906. }, mustEnableAttachments)
  907. })
  908. }, mustEnableIssuesOrPulls)
  909. m.Group("/labels", func() {
  910. m.Combo("").Get(repo.ListLabels).
  911. Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel)
  912. m.Combo("/{id}").Get(repo.GetLabel).
  913. Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel).
  914. Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteLabel)
  915. })
  916. m.Post("/markdown", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MarkdownOption{}), misc.Markdown)
  917. m.Post("/markdown/raw", reqToken(auth_model.AccessTokenScopeRepo), misc.MarkdownRaw)
  918. m.Group("/milestones", func() {
  919. m.Combo("").Get(repo.ListMilestones).
  920. Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
  921. m.Combo("/{id}").Get(repo.GetMilestone).
  922. Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
  923. Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
  924. })
  925. m.Get("/stargazers", repo.ListStargazers)
  926. m.Get("/subscribers", repo.ListSubscribers)
  927. m.Group("/subscription", func() {
  928. m.Get("", user.IsWatching)
  929. m.Put("", reqToken(auth_model.AccessTokenScopeRepo), user.Watch)
  930. m.Delete("", reqToken(auth_model.AccessTokenScopeRepo), user.Unwatch)
  931. })
  932. m.Group("/releases", func() {
  933. m.Combo("").Get(repo.ListReleases).
  934. Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
  935. m.Group("/{id}", func() {
  936. m.Combo("").Get(repo.GetRelease).
  937. Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease).
  938. Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.DeleteRelease)
  939. m.Group("/assets", func() {
  940. m.Combo("").Get(repo.ListReleaseAttachments).
  941. Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment)
  942. m.Combo("/{asset}").Get(repo.GetReleaseAttachment).
  943. Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
  944. Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment)
  945. })
  946. })
  947. m.Group("/tags", func() {
  948. m.Combo("/{tag}").
  949. Get(repo.GetReleaseByTag).
  950. Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
  951. })
  952. }, reqRepoReader(unit.TypeReleases))
  953. m.Post("/mirror-sync", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), repo.MirrorSync)
  954. m.Post("/push_mirrors-sync", reqAdmin(), reqToken(auth_model.AccessTokenScopeRepo), repo.PushMirrorSync)
  955. m.Group("/push_mirrors", func() {
  956. m.Combo("").Get(repo.ListPushMirrors).
  957. Post(bind(api.CreatePushMirrorOption{}), repo.AddPushMirror)
  958. m.Combo("/{name}").
  959. Delete(repo.DeletePushMirrorByRemoteName).
  960. Get(repo.GetPushMirrorByName)
  961. }, reqAdmin(), reqToken(auth_model.AccessTokenScopeRepo))
  962. m.Get("/editorconfig/{filename}", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetEditorconfig)
  963. m.Group("/pulls", func() {
  964. m.Combo("").Get(repo.ListPullRequests).
  965. Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
  966. m.Group("/{index}", func() {
  967. m.Combo("").Get(repo.GetPullRequest).
  968. Patch(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
  969. m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch)
  970. m.Post("/update", reqToken(auth_model.AccessTokenScopeRepo), repo.UpdatePullRequest)
  971. m.Get("/commits", repo.GetPullRequestCommits)
  972. m.Get("/files", repo.GetPullRequestFiles)
  973. m.Combo("/merge").Get(repo.IsPullRequestMerged).
  974. Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest).
  975. Delete(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.CancelScheduledAutoMerge)
  976. m.Group("/reviews", func() {
  977. m.Combo("").
  978. Get(repo.ListPullReviews).
  979. Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)
  980. m.Group("/{id}", func() {
  981. m.Combo("").
  982. Get(repo.GetPullReview).
  983. Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.DeletePullReview).
  984. Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)
  985. m.Combo("/comments").
  986. Get(repo.GetPullReviewComments)
  987. m.Post("/dismissals", reqToken(auth_model.AccessTokenScopeRepo), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview)
  988. m.Post("/undismissals", reqToken(auth_model.AccessTokenScopeRepo), repo.UnDismissPullReview)
  989. })
  990. })
  991. m.Combo("/requested_reviewers", reqToken(auth_model.AccessTokenScopeRepo)).
  992. Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
  993. Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
  994. })
  995. }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
  996. m.Group("/statuses", func() {
  997. m.Combo("/{sha}").Get(repo.GetCommitStatuses).
  998. Post(reqToken(auth_model.AccessTokenScopeRepoStatus), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
  999. }, reqRepoReader(unit.TypeCode))
  1000. m.Group("/commits", func() {
  1001. m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits)
  1002. m.Group("/{ref}", func() {
  1003. m.Get("/status", repo.GetCombinedCommitStatusByRef)
  1004. m.Get("/statuses", repo.GetCommitStatusesByRef)
  1005. }, context.ReferencesGitRepo())
  1006. }, reqRepoReader(unit.TypeCode))
  1007. m.Group("/git", func() {
  1008. m.Group("/commits", func() {
  1009. m.Get("/{sha}", repo.GetSingleCommit)
  1010. m.Get("/{sha}.{diffType:diff|patch}", repo.DownloadCommitDiffOrPatch)
  1011. })
  1012. m.Get("/refs", repo.GetGitAllRefs)
  1013. m.Get("/refs/*", repo.GetGitRefs)
  1014. m.Get("/trees/{sha}", repo.GetTree)
  1015. m.Get("/blobs/{sha}", repo.GetBlob)
  1016. m.Get("/tags/{sha}", repo.GetAnnotatedTag)
  1017. m.Get("/notes/{sha}", repo.GetNote)
  1018. }, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
  1019. m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(auth_model.AccessTokenScopeRepo), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch)
  1020. m.Group("/contents", func() {
  1021. m.Get("", repo.GetContentsList)
  1022. m.Get("/*", repo.GetContents)
  1023. m.Group("/*", func() {
  1024. m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, repo.CreateFile)
  1025. m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, repo.UpdateFile)
  1026. m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, repo.DeleteFile)
  1027. }, reqToken(auth_model.AccessTokenScopeRepo))
  1028. }, reqRepoReader(unit.TypeCode))
  1029. m.Get("/signing-key.gpg", misc.SigningKey)
  1030. m.Group("/topics", func() {
  1031. m.Combo("").Get(repo.ListTopics).
  1032. Put(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
  1033. m.Group("/{topic}", func() {
  1034. m.Combo("").Put(reqToken(auth_model.AccessTokenScopeRepo), repo.AddTopic).
  1035. Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteTopic)
  1036. }, reqAdmin())
  1037. }, reqAnyRepoReader())
  1038. m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates)
  1039. m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
  1040. }, repoAssignment())
  1041. })
  1042. // NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
  1043. m.Group("/packages/{username}", func() {
  1044. m.Group("/{type}/{name}/{version}", func() {
  1045. m.Get("", reqToken(auth_model.AccessTokenScopeReadPackage), packages.GetPackage)
  1046. m.Delete("", reqToken(auth_model.AccessTokenScopeDeletePackage), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
  1047. m.Get("/files", reqToken(auth_model.AccessTokenScopeReadPackage), packages.ListPackageFiles)
  1048. })
  1049. m.Get("/", reqToken(auth_model.AccessTokenScopeReadPackage), packages.ListPackages)
  1050. }, context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
  1051. // Organizations
  1052. m.Get("/user/orgs", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListMyOrgs)
  1053. m.Group("/users/{username}/orgs", func() {
  1054. m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListUserOrgs)
  1055. m.Get("/{org}/permissions", reqToken(auth_model.AccessTokenScopeReadOrg), org.GetUserOrgsPermissions)
  1056. }, context_service.UserAssignmentAPI())
  1057. m.Post("/orgs", reqToken(auth_model.AccessTokenScopeWriteOrg), bind(api.CreateOrgOption{}), org.Create)
  1058. m.Get("/orgs", reqToken(auth_model.AccessTokenScopeReadOrg), org.GetAll)
  1059. m.Group("/orgs/{org}", func() {
  1060. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.Get).
  1061. Patch(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
  1062. Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.Delete)
  1063. m.Combo("/repos").Get(reqToken(auth_model.AccessTokenScopeReadOrg), user.ListOrgRepos).
  1064. Post(reqToken(auth_model.AccessTokenScopeWriteOrg), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
  1065. m.Group("/members", func() {
  1066. m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListMembers)
  1067. m.Combo("/{username}").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.IsMember).
  1068. Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.DeleteMember)
  1069. })
  1070. m.Group("/public_members", func() {
  1071. m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListPublicMembers)
  1072. m.Combo("/{username}").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.IsPublicMember).
  1073. Put(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgMembership(), org.PublicizeMember).
  1074. Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgMembership(), org.ConcealMember)
  1075. })
  1076. m.Group("/teams", func() {
  1077. m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListTeams)
  1078. m.Post("", reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
  1079. m.Get("/search", reqToken(auth_model.AccessTokenScopeReadOrg), org.SearchTeam)
  1080. }, reqOrgMembership())
  1081. m.Group("/labels", func() {
  1082. m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListLabels)
  1083. m.Post("", reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
  1084. m.Combo("/{id}").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetLabel).
  1085. Patch(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
  1086. Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.DeleteLabel)
  1087. })
  1088. m.Group("/hooks", func() {
  1089. m.Combo("").Get(org.ListHooks).
  1090. Post(bind(api.CreateHookOption{}), org.CreateHook)
  1091. m.Combo("/{id}").Get(org.GetHook).
  1092. Patch(bind(api.EditHookOption{}), org.EditHook).
  1093. Delete(org.DeleteHook)
  1094. }, reqToken(auth_model.AccessTokenScopeAdminOrgHook), reqOrgOwnership(), reqWebhooksEnabled())
  1095. }, orgAssignment(true))
  1096. m.Group("/teams/{teamid}", func() {
  1097. m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeam).
  1098. Patch(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
  1099. Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.DeleteTeam)
  1100. m.Group("/members", func() {
  1101. m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamMembers)
  1102. m.Combo("/{username}").
  1103. Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamMember).
  1104. Put(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.AddTeamMember).
  1105. Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.RemoveTeamMember)
  1106. })
  1107. m.Group("/repos", func() {
  1108. m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamRepos)
  1109. m.Combo("/{org}/{reponame}").
  1110. Put(reqToken(auth_model.AccessTokenScopeWriteOrg), org.AddTeamRepository).
  1111. Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), org.RemoveTeamRepository).
  1112. Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamRepo)
  1113. })
  1114. }, orgAssignment(false, true), reqToken(""), reqTeamMembership())
  1115. m.Group("/admin", func() {
  1116. m.Group("/cron", func() {
  1117. m.Get("", admin.ListCronTasks)
  1118. m.Post("/{task}", admin.PostCronTask)
  1119. })
  1120. m.Get("/orgs", admin.GetAllOrgs)
  1121. m.Group("/users", func() {
  1122. m.Get("", admin.GetAllUsers)
  1123. m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
  1124. m.Group("/{username}", func() {
  1125. m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
  1126. Delete(admin.DeleteUser)
  1127. m.Group("/keys", func() {
  1128. m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
  1129. m.Delete("/{id}", admin.DeleteUserPublicKey)
  1130. })
  1131. m.Get("/orgs", org.ListUserOrgs)
  1132. m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
  1133. m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
  1134. }, context_service.UserAssignmentAPI())
  1135. })
  1136. m.Group("/unadopted", func() {
  1137. m.Get("", admin.ListUnadoptedRepositories)
  1138. m.Post("/{username}/{reponame}", admin.AdoptRepository)
  1139. m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository)
  1140. })
  1141. }, reqToken(auth_model.AccessTokenScopeSudo), reqSiteAdmin())
  1142. m.Group("/topics", func() {
  1143. m.Get("/search", repo.TopicSearch)
  1144. })
  1145. }, sudo())
  1146. return m
  1147. }
  1148. func securityHeaders() func(http.Handler) http.Handler {
  1149. return func(next http.Handler) http.Handler {
  1150. return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  1151. // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
  1152. // http://stackoverflow.com/a/3146618/244009
  1153. resp.Header().Set("x-content-type-options", "nosniff")
  1154. next.ServeHTTP(resp, req)
  1155. })
  1156. }
  1157. }