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 52KB

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