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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2016 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. // Package v1 Gitea API.
  6. //
  7. // This documentation describes the Gitea API.
  8. //
  9. // Schemes: http, https
  10. // BasePath: /api/v1
  11. // Version: 1.1.1
  12. // License: MIT http://opensource.org/licenses/MIT
  13. //
  14. // Consumes:
  15. // - application/json
  16. // - text/plain
  17. //
  18. // Produces:
  19. // - application/json
  20. // - text/html
  21. //
  22. // Security:
  23. // - BasicAuth :
  24. // - Token :
  25. // - AccessToken :
  26. // - AuthorizationHeaderToken :
  27. // - SudoParam :
  28. // - SudoHeader :
  29. // - TOTPHeader :
  30. //
  31. // SecurityDefinitions:
  32. // BasicAuth:
  33. // type: basic
  34. // Token:
  35. // type: apiKey
  36. // name: token
  37. // in: query
  38. // AccessToken:
  39. // type: apiKey
  40. // name: access_token
  41. // in: query
  42. // AuthorizationHeaderToken:
  43. // type: apiKey
  44. // name: Authorization
  45. // in: header
  46. // description: API tokens must be prepended with "token" followed by a space.
  47. // SudoParam:
  48. // type: apiKey
  49. // name: sudo
  50. // in: query
  51. // description: Sudo API request as the user provided as the key. Admin privileges are required.
  52. // SudoHeader:
  53. // type: apiKey
  54. // name: Sudo
  55. // in: header
  56. // description: Sudo API request as the user provided as the key. Admin privileges are required.
  57. // TOTPHeader:
  58. // type: apiKey
  59. // name: X-GITEA-OTP
  60. // in: header
  61. // description: Must be used in combination with BasicAuth if two-factor authentication is enabled.
  62. //
  63. // swagger:meta
  64. package v1
  65. import (
  66. "net/http"
  67. "strings"
  68. "code.gitea.io/gitea/models"
  69. "code.gitea.io/gitea/modules/auth"
  70. "code.gitea.io/gitea/modules/context"
  71. "code.gitea.io/gitea/modules/log"
  72. "code.gitea.io/gitea/modules/setting"
  73. api "code.gitea.io/gitea/modules/structs"
  74. "code.gitea.io/gitea/routers/api/v1/admin"
  75. "code.gitea.io/gitea/routers/api/v1/misc"
  76. "code.gitea.io/gitea/routers/api/v1/notify"
  77. "code.gitea.io/gitea/routers/api/v1/org"
  78. "code.gitea.io/gitea/routers/api/v1/repo"
  79. "code.gitea.io/gitea/routers/api/v1/settings"
  80. _ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
  81. "code.gitea.io/gitea/routers/api/v1/user"
  82. "gitea.com/macaron/binding"
  83. "gitea.com/macaron/macaron"
  84. )
  85. func sudo() macaron.Handler {
  86. return func(ctx *context.APIContext) {
  87. sudo := ctx.Query("sudo")
  88. if len(sudo) == 0 {
  89. sudo = ctx.Req.Header.Get("Sudo")
  90. }
  91. if len(sudo) > 0 {
  92. if ctx.IsSigned && ctx.User.IsAdmin {
  93. user, err := models.GetUserByName(sudo)
  94. if err != nil {
  95. if models.IsErrUserNotExist(err) {
  96. ctx.NotFound()
  97. } else {
  98. ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
  99. }
  100. return
  101. }
  102. log.Trace("Sudo from (%s) to: %s", ctx.User.Name, user.Name)
  103. ctx.User = user
  104. } else {
  105. ctx.JSON(http.StatusForbidden, map[string]string{
  106. "message": "Only administrators allowed to sudo.",
  107. })
  108. return
  109. }
  110. }
  111. }
  112. }
  113. func repoAssignment() macaron.Handler {
  114. return func(ctx *context.APIContext) {
  115. userName := ctx.Params(":username")
  116. repoName := ctx.Params(":reponame")
  117. var (
  118. owner *models.User
  119. err error
  120. )
  121. // Check if the user is the same as the repository owner.
  122. if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
  123. owner = ctx.User
  124. } else {
  125. owner, err = models.GetUserByName(userName)
  126. if err != nil {
  127. if models.IsErrUserNotExist(err) {
  128. if redirectUserID, err := models.LookupUserRedirect(userName); err == nil {
  129. context.RedirectToUser(ctx.Context, userName, redirectUserID)
  130. } else if models.IsErrUserRedirectNotExist(err) {
  131. ctx.NotFound("GetUserByName", err)
  132. } else {
  133. ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
  134. }
  135. } else {
  136. ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
  137. }
  138. return
  139. }
  140. }
  141. ctx.Repo.Owner = owner
  142. // Get repository.
  143. repo, err := models.GetRepositoryByName(owner.ID, repoName)
  144. if err != nil {
  145. if models.IsErrRepoNotExist(err) {
  146. redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
  147. if err == nil {
  148. context.RedirectToRepo(ctx.Context, redirectRepoID)
  149. } else if models.IsErrRepoRedirectNotExist(err) {
  150. ctx.NotFound()
  151. } else {
  152. ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err)
  153. }
  154. } else {
  155. ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
  156. }
  157. return
  158. }
  159. repo.Owner = owner
  160. ctx.Repo.Repository = repo
  161. ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.User)
  162. if err != nil {
  163. ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
  164. return
  165. }
  166. if !ctx.Repo.HasAccess() {
  167. ctx.NotFound()
  168. return
  169. }
  170. }
  171. }
  172. // Contexter middleware already checks token for user sign in process.
  173. func reqToken() macaron.Handler {
  174. return func(ctx *context.APIContext) {
  175. if true == ctx.Data["IsApiToken"] {
  176. return
  177. }
  178. if ctx.Context.IsBasicAuth {
  179. ctx.CheckForOTP()
  180. return
  181. }
  182. if ctx.IsSigned {
  183. ctx.RequireCSRF()
  184. return
  185. }
  186. ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
  187. }
  188. }
  189. func reqBasicAuth() macaron.Handler {
  190. return func(ctx *context.APIContext) {
  191. if !ctx.Context.IsBasicAuth {
  192. ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "basic auth required")
  193. return
  194. }
  195. ctx.CheckForOTP()
  196. }
  197. }
  198. // reqSiteAdmin user should be the site admin
  199. func reqSiteAdmin() macaron.Handler {
  200. return func(ctx *context.APIContext) {
  201. if !ctx.IsUserSiteAdmin() {
  202. ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
  203. return
  204. }
  205. }
  206. }
  207. // reqOwner user should be the owner of the repo or site admin.
  208. func reqOwner() macaron.Handler {
  209. return func(ctx *context.APIContext) {
  210. if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() {
  211. ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
  212. return
  213. }
  214. }
  215. }
  216. // reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin
  217. func reqAdmin() macaron.Handler {
  218. return func(ctx *context.APIContext) {
  219. if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  220. ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
  221. return
  222. }
  223. }
  224. }
  225. // reqRepoWriter user should have a permission to write to a repo, or be a site admin
  226. func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
  227. return func(ctx *context.APIContext) {
  228. if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  229. ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
  230. return
  231. }
  232. }
  233. }
  234. // reqRepoReader user should have specific read permission or be a repo admin or a site admin
  235. func reqRepoReader(unitType models.UnitType) macaron.Handler {
  236. return func(ctx *context.APIContext) {
  237. if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  238. ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
  239. return
  240. }
  241. }
  242. }
  243. // reqAnyRepoReader user should have any permission to read repository or permissions of site admin
  244. func reqAnyRepoReader() macaron.Handler {
  245. return func(ctx *context.APIContext) {
  246. if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() {
  247. ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
  248. return
  249. }
  250. }
  251. }
  252. // reqOrgOwnership user should be an organization owner, or a site admin
  253. func reqOrgOwnership() macaron.Handler {
  254. return func(ctx *context.APIContext) {
  255. if ctx.Context.IsUserSiteAdmin() {
  256. return
  257. }
  258. var orgID int64
  259. if ctx.Org.Organization != nil {
  260. orgID = ctx.Org.Organization.ID
  261. } else if ctx.Org.Team != nil {
  262. orgID = ctx.Org.Team.OrgID
  263. } else {
  264. ctx.Error(http.StatusInternalServerError, "", "reqOrgOwnership: unprepared context")
  265. return
  266. }
  267. isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID)
  268. if err != nil {
  269. ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
  270. return
  271. } else if !isOwner {
  272. if ctx.Org.Organization != nil {
  273. ctx.Error(http.StatusForbidden, "", "Must be an organization owner")
  274. } else {
  275. ctx.NotFound()
  276. }
  277. return
  278. }
  279. }
  280. }
  281. // reqTeamMembership user should be an team member, or a site admin
  282. func reqTeamMembership() macaron.Handler {
  283. return func(ctx *context.APIContext) {
  284. if ctx.Context.IsUserSiteAdmin() {
  285. return
  286. }
  287. if ctx.Org.Team == nil {
  288. ctx.Error(http.StatusInternalServerError, "", "reqTeamMembership: unprepared context")
  289. return
  290. }
  291. var orgID = ctx.Org.Team.OrgID
  292. isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID)
  293. if err != nil {
  294. ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
  295. return
  296. } else if isOwner {
  297. return
  298. }
  299. if isTeamMember, err := models.IsTeamMember(orgID, ctx.Org.Team.ID, ctx.User.ID); err != nil {
  300. ctx.Error(http.StatusInternalServerError, "IsTeamMember", err)
  301. return
  302. } else if !isTeamMember {
  303. isOrgMember, err := models.IsOrganizationMember(orgID, ctx.User.ID)
  304. if err != nil {
  305. ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
  306. } else if isOrgMember {
  307. ctx.Error(http.StatusForbidden, "", "Must be a team member")
  308. } else {
  309. ctx.NotFound()
  310. }
  311. return
  312. }
  313. }
  314. }
  315. // reqOrgMembership user should be an organization member, or a site admin
  316. func reqOrgMembership() macaron.Handler {
  317. return func(ctx *context.APIContext) {
  318. if ctx.Context.IsUserSiteAdmin() {
  319. return
  320. }
  321. var orgID int64
  322. if ctx.Org.Organization != nil {
  323. orgID = ctx.Org.Organization.ID
  324. } else if ctx.Org.Team != nil {
  325. orgID = ctx.Org.Team.OrgID
  326. } else {
  327. ctx.Error(http.StatusInternalServerError, "", "reqOrgMembership: unprepared context")
  328. return
  329. }
  330. if isMember, err := models.IsOrganizationMember(orgID, ctx.User.ID); err != nil {
  331. ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
  332. return
  333. } else if !isMember {
  334. if ctx.Org.Organization != nil {
  335. ctx.Error(http.StatusForbidden, "", "Must be an organization member")
  336. } else {
  337. ctx.NotFound()
  338. }
  339. return
  340. }
  341. }
  342. }
  343. func reqGitHook() macaron.Handler {
  344. return func(ctx *context.APIContext) {
  345. if !ctx.User.CanEditGitHook() {
  346. ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
  347. return
  348. }
  349. }
  350. }
  351. func orgAssignment(args ...bool) macaron.Handler {
  352. var (
  353. assignOrg bool
  354. assignTeam bool
  355. )
  356. if len(args) > 0 {
  357. assignOrg = args[0]
  358. }
  359. if len(args) > 1 {
  360. assignTeam = args[1]
  361. }
  362. return func(ctx *context.APIContext) {
  363. ctx.Org = new(context.APIOrganization)
  364. var err error
  365. if assignOrg {
  366. ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":org"))
  367. if err != nil {
  368. if models.IsErrOrgNotExist(err) {
  369. redirectUserID, err := models.LookupUserRedirect(ctx.Params(":org"))
  370. if err == nil {
  371. context.RedirectToUser(ctx.Context, ctx.Params(":org"), redirectUserID)
  372. } else if models.IsErrUserRedirectNotExist(err) {
  373. ctx.NotFound("GetOrgByName", err)
  374. } else {
  375. ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
  376. }
  377. } else {
  378. ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
  379. }
  380. return
  381. }
  382. }
  383. if assignTeam {
  384. ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid"))
  385. if err != nil {
  386. if models.IsErrTeamNotExist(err) {
  387. ctx.NotFound()
  388. } else {
  389. ctx.Error(http.StatusInternalServerError, "GetTeamById", err)
  390. }
  391. return
  392. }
  393. }
  394. }
  395. }
  396. func mustEnableIssues(ctx *context.APIContext) {
  397. if !ctx.Repo.CanRead(models.UnitTypeIssues) {
  398. if log.IsTrace() {
  399. if ctx.IsSigned {
  400. log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
  401. "User in Repo has Permissions: %-+v",
  402. ctx.User,
  403. models.UnitTypeIssues,
  404. ctx.Repo.Repository,
  405. ctx.Repo.Permission)
  406. } else {
  407. log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
  408. "Anonymous user in Repo has Permissions: %-+v",
  409. models.UnitTypeIssues,
  410. ctx.Repo.Repository,
  411. ctx.Repo.Permission)
  412. }
  413. }
  414. ctx.NotFound()
  415. return
  416. }
  417. }
  418. func mustAllowPulls(ctx *context.APIContext) {
  419. if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(models.UnitTypePullRequests)) {
  420. if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
  421. if ctx.IsSigned {
  422. log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
  423. "User in Repo has Permissions: %-+v",
  424. ctx.User,
  425. models.UnitTypePullRequests,
  426. ctx.Repo.Repository,
  427. ctx.Repo.Permission)
  428. } else {
  429. log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
  430. "Anonymous user in Repo has Permissions: %-+v",
  431. models.UnitTypePullRequests,
  432. ctx.Repo.Repository,
  433. ctx.Repo.Permission)
  434. }
  435. }
  436. ctx.NotFound()
  437. return
  438. }
  439. }
  440. func mustEnableIssuesOrPulls(ctx *context.APIContext) {
  441. if !ctx.Repo.CanRead(models.UnitTypeIssues) &&
  442. !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(models.UnitTypePullRequests)) {
  443. if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
  444. if ctx.IsSigned {
  445. log.Trace("Permission Denied: User %-v cannot read %-v and %-v in Repo %-v\n"+
  446. "User in Repo has Permissions: %-+v",
  447. ctx.User,
  448. models.UnitTypeIssues,
  449. models.UnitTypePullRequests,
  450. ctx.Repo.Repository,
  451. ctx.Repo.Permission)
  452. } else {
  453. log.Trace("Permission Denied: Anonymous user cannot read %-v and %-v in Repo %-v\n"+
  454. "Anonymous user in Repo has Permissions: %-+v",
  455. models.UnitTypeIssues,
  456. models.UnitTypePullRequests,
  457. ctx.Repo.Repository,
  458. ctx.Repo.Permission)
  459. }
  460. }
  461. ctx.NotFound()
  462. return
  463. }
  464. }
  465. func mustEnableUserHeatmap(ctx *context.APIContext) {
  466. if !setting.Service.EnableUserHeatmap {
  467. ctx.NotFound()
  468. return
  469. }
  470. }
  471. func mustNotBeArchived(ctx *context.APIContext) {
  472. if ctx.Repo.Repository.IsArchived {
  473. ctx.NotFound()
  474. return
  475. }
  476. }
  477. // RegisterRoutes registers all v1 APIs routes to web application.
  478. func RegisterRoutes(m *macaron.Macaron) {
  479. bind := binding.Bind
  480. if setting.API.EnableSwagger {
  481. m.Get("/swagger", misc.Swagger) // Render V1 by default
  482. }
  483. m.Group("/v1", func() {
  484. // Miscellaneous
  485. if setting.API.EnableSwagger {
  486. m.Get("/swagger", misc.Swagger)
  487. }
  488. m.Get("/version", misc.Version)
  489. m.Get("/signing-key.gpg", misc.SigningKey)
  490. m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
  491. m.Post("/markdown/raw", misc.MarkdownRaw)
  492. m.Group("/settings", func() {
  493. m.Get("/ui", settings.GetGeneralUISettings)
  494. m.Get("/api", settings.GetGeneralAPISettings)
  495. m.Get("/attachment", settings.GetGeneralAttachmentSettings)
  496. m.Get("/repository", settings.GetGeneralRepoSettings)
  497. })
  498. // Notifications
  499. m.Group("/notifications", func() {
  500. m.Combo("").
  501. Get(notify.ListNotifications).
  502. Put(notify.ReadNotifications)
  503. m.Get("/new", notify.NewAvailable)
  504. m.Combo("/threads/:id").
  505. Get(notify.GetThread).
  506. Patch(notify.ReadThread)
  507. }, reqToken())
  508. // Users
  509. m.Group("/users", func() {
  510. m.Get("/search", user.Search)
  511. m.Group("/:username", func() {
  512. m.Get("", user.GetInfo)
  513. m.Get("/heatmap", mustEnableUserHeatmap, user.GetUserHeatmapData)
  514. m.Get("/repos", user.ListUserRepos)
  515. m.Group("/tokens", func() {
  516. m.Combo("").Get(user.ListAccessTokens).
  517. Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
  518. m.Combo("/:id").Delete(user.DeleteAccessToken)
  519. }, reqBasicAuth())
  520. })
  521. })
  522. m.Group("/users", func() {
  523. m.Group("/:username", func() {
  524. m.Get("/keys", user.ListPublicKeys)
  525. m.Get("/gpg_keys", user.ListGPGKeys)
  526. m.Get("/followers", user.ListFollowers)
  527. m.Group("/following", func() {
  528. m.Get("", user.ListFollowing)
  529. m.Get("/:target", user.CheckFollowing)
  530. })
  531. m.Get("/starred", user.GetStarredRepos)
  532. m.Get("/subscriptions", user.GetWatchedRepos)
  533. })
  534. }, reqToken())
  535. m.Group("/user", func() {
  536. m.Get("", user.GetAuthenticatedUser)
  537. m.Combo("/emails").Get(user.ListEmails).
  538. Post(bind(api.CreateEmailOption{}), user.AddEmail).
  539. Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
  540. m.Get("/followers", user.ListMyFollowers)
  541. m.Group("/following", func() {
  542. m.Get("", user.ListMyFollowing)
  543. m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
  544. })
  545. m.Group("/keys", func() {
  546. m.Combo("").Get(user.ListMyPublicKeys).
  547. Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
  548. m.Combo("/:id").Get(user.GetPublicKey).
  549. Delete(user.DeletePublicKey)
  550. })
  551. m.Group("/applications", func() {
  552. m.Combo("/oauth2").
  553. Get(user.ListOauth2Applications).
  554. Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application)
  555. m.Combo("/oauth2/:id").
  556. Delete(user.DeleteOauth2Application).
  557. Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
  558. Get(user.GetOauth2Application)
  559. }, reqToken())
  560. m.Group("/gpg_keys", func() {
  561. m.Combo("").Get(user.ListMyGPGKeys).
  562. Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
  563. m.Combo("/:id").Get(user.GetGPGKey).
  564. Delete(user.DeleteGPGKey)
  565. })
  566. m.Combo("/repos").Get(user.ListMyRepos).
  567. Post(bind(api.CreateRepoOption{}), repo.Create)
  568. m.Group("/starred", func() {
  569. m.Get("", user.GetMyStarredRepos)
  570. m.Group("/:username/:reponame", func() {
  571. m.Get("", user.IsStarring)
  572. m.Put("", user.Star)
  573. m.Delete("", user.Unstar)
  574. }, repoAssignment())
  575. })
  576. m.Get("/times", repo.ListMyTrackedTimes)
  577. m.Get("/stopwatches", repo.GetStopwatches)
  578. m.Get("/subscriptions", user.GetMyWatchedRepos)
  579. m.Get("/teams", org.ListUserTeams)
  580. }, reqToken())
  581. // Repositories
  582. m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated)
  583. m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)
  584. m.Group("/repos", func() {
  585. m.Get("/search", repo.Search)
  586. m.Get("/issues/search", repo.SearchIssues)
  587. m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
  588. m.Group("/:username/:reponame", func() {
  589. m.Combo("").Get(reqAnyRepoReader(), repo.Get).
  590. Delete(reqToken(), reqOwner(), repo.Delete).
  591. Patch(reqToken(), reqAdmin(), context.RepoRefForAPI(), bind(api.EditRepoOption{}), repo.Edit)
  592. m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
  593. m.Combo("/notifications").
  594. Get(reqToken(), notify.ListRepoNotifications).
  595. Put(reqToken(), notify.ReadRepoNotifications)
  596. m.Group("/hooks", func() {
  597. m.Combo("").Get(repo.ListHooks).
  598. Post(bind(api.CreateHookOption{}), repo.CreateHook)
  599. m.Group("/:id", func() {
  600. m.Combo("").Get(repo.GetHook).
  601. Patch(bind(api.EditHookOption{}), repo.EditHook).
  602. Delete(repo.DeleteHook)
  603. m.Post("/tests", context.RepoRefForAPI(), repo.TestHook)
  604. })
  605. m.Group("/git", func() {
  606. m.Combo("").Get(repo.ListGitHooks)
  607. m.Group("/:id", func() {
  608. m.Combo("").Get(repo.GetGitHook).
  609. Patch(bind(api.EditGitHookOption{}), repo.EditGitHook).
  610. Delete(repo.DeleteGitHook)
  611. })
  612. }, reqGitHook(), context.ReferencesGitRepo(true))
  613. }, reqToken(), reqAdmin())
  614. m.Group("/collaborators", func() {
  615. m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
  616. m.Combo("/:collaborator").Get(reqAnyRepoReader(), repo.IsCollaborator).
  617. Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
  618. Delete(reqAdmin(), repo.DeleteCollaborator)
  619. }, reqToken())
  620. m.Get("/raw/*", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
  621. m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive)
  622. m.Combo("/forks").Get(repo.ListForks).
  623. Post(reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
  624. m.Group("/branches", func() {
  625. m.Get("", repo.ListBranches)
  626. m.Get("/*", repo.GetBranch)
  627. m.Delete("/*", context.ReferencesGitRepo(false), reqRepoWriter(models.UnitTypeCode), repo.DeleteBranch)
  628. m.Post("", reqRepoWriter(models.UnitTypeCode), bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
  629. }, reqRepoReader(models.UnitTypeCode))
  630. m.Group("/branch_protections", func() {
  631. m.Get("", repo.ListBranchProtections)
  632. m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection)
  633. m.Group("/:name", func() {
  634. m.Get("", repo.GetBranchProtection)
  635. m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection)
  636. m.Delete("", repo.DeleteBranchProtection)
  637. })
  638. }, reqToken(), reqAdmin())
  639. m.Group("/tags", func() {
  640. m.Get("", repo.ListTags)
  641. }, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true))
  642. m.Group("/keys", func() {
  643. m.Combo("").Get(repo.ListDeployKeys).
  644. Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
  645. m.Combo("/:id").Get(repo.GetDeployKey).
  646. Delete(repo.DeleteDeploykey)
  647. }, reqToken(), reqAdmin())
  648. m.Group("/times", func() {
  649. m.Combo("").Get(repo.ListTrackedTimesByRepository)
  650. m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser)
  651. }, mustEnableIssues, reqToken())
  652. m.Group("/issues", func() {
  653. m.Combo("").Get(repo.ListIssues).
  654. Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
  655. m.Group("/comments", func() {
  656. m.Get("", repo.ListRepoIssueComments)
  657. m.Group("/:id", func() {
  658. m.Combo("").
  659. Get(repo.GetIssueComment).
  660. Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
  661. Delete(reqToken(), repo.DeleteIssueComment)
  662. m.Combo("/reactions").
  663. Get(repo.GetIssueCommentReactions).
  664. Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
  665. Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
  666. })
  667. })
  668. m.Group("/:index", func() {
  669. m.Combo("").Get(repo.GetIssue).
  670. Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
  671. m.Group("/comments", func() {
  672. m.Combo("").Get(repo.ListIssueComments).
  673. Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
  674. m.Combo("/:id", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
  675. Delete(repo.DeleteIssueCommentDeprecated)
  676. })
  677. m.Group("/labels", func() {
  678. m.Combo("").Get(repo.ListIssueLabels).
  679. Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
  680. Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
  681. Delete(reqToken(), repo.ClearIssueLabels)
  682. m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
  683. })
  684. m.Group("/times", func() {
  685. m.Combo("").
  686. Get(repo.ListTrackedTimes).
  687. Post(bind(api.AddTimeOption{}), repo.AddTime).
  688. Delete(repo.ResetIssueTime)
  689. m.Delete("/:id", repo.DeleteTime)
  690. }, reqToken())
  691. m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
  692. m.Group("/stopwatch", func() {
  693. m.Post("/start", reqToken(), repo.StartIssueStopwatch)
  694. m.Post("/stop", reqToken(), repo.StopIssueStopwatch)
  695. m.Delete("/delete", reqToken(), repo.DeleteIssueStopwatch)
  696. })
  697. m.Group("/subscriptions", func() {
  698. m.Get("", repo.GetIssueSubscribers)
  699. m.Get("/check", reqToken(), repo.CheckIssueSubscription)
  700. m.Put("/:user", reqToken(), repo.AddIssueSubscription)
  701. m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
  702. })
  703. m.Combo("/reactions").
  704. Get(repo.GetIssueReactions).
  705. Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueReaction).
  706. Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
  707. })
  708. }, mustEnableIssuesOrPulls)
  709. m.Group("/labels", func() {
  710. m.Combo("").Get(repo.ListLabels).
  711. Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel)
  712. m.Combo("/:id").Get(repo.GetLabel).
  713. Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel).
  714. Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteLabel)
  715. })
  716. m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
  717. m.Post("/markdown/raw", misc.MarkdownRaw)
  718. m.Group("/milestones", func() {
  719. m.Combo("").Get(repo.ListMilestones).
  720. Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
  721. m.Combo("/:id").Get(repo.GetMilestone).
  722. Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
  723. Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteMilestone)
  724. })
  725. m.Get("/stargazers", repo.ListStargazers)
  726. m.Get("/subscribers", repo.ListSubscribers)
  727. m.Group("/subscription", func() {
  728. m.Get("", user.IsWatching)
  729. m.Put("", reqToken(), user.Watch)
  730. m.Delete("", reqToken(), user.Unwatch)
  731. })
  732. m.Group("/releases", func() {
  733. m.Combo("").Get(repo.ListReleases).
  734. Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.CreateReleaseOption{}), repo.CreateRelease)
  735. m.Group("/:id", func() {
  736. m.Combo("").Get(repo.GetRelease).
  737. Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.EditReleaseOption{}), repo.EditRelease).
  738. Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteRelease)
  739. m.Group("/assets", func() {
  740. m.Combo("").Get(repo.ListReleaseAttachments).
  741. Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.CreateReleaseAttachment)
  742. m.Combo("/:asset").Get(repo.GetReleaseAttachment).
  743. Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
  744. Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseAttachment)
  745. })
  746. })
  747. m.Group("/tags", func() {
  748. m.Combo("/:tag").
  749. Get(repo.GetReleaseTag).
  750. Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag)
  751. })
  752. }, reqRepoReader(models.UnitTypeReleases))
  753. m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync)
  754. m.Get("/editorconfig/:filename", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig)
  755. m.Group("/pulls", func() {
  756. m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).
  757. Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
  758. m.Group("/:index", func() {
  759. m.Combo("").Get(repo.GetPullRequest).
  760. Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
  761. m.Get(".diff", repo.DownloadPullDiff)
  762. m.Get(".patch", repo.DownloadPullPatch)
  763. m.Post("/update", reqToken(), repo.UpdatePullRequest)
  764. m.Combo("/merge").Get(repo.IsPullRequestMerged).
  765. Post(reqToken(), mustNotBeArchived, bind(auth.MergePullRequestForm{}), repo.MergePullRequest)
  766. m.Group("/reviews", func() {
  767. m.Combo("").
  768. Get(repo.ListPullReviews).
  769. Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)
  770. m.Group("/:id", func() {
  771. m.Combo("").
  772. Get(repo.GetPullReview).
  773. Delete(reqToken(), repo.DeletePullReview).
  774. Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)
  775. m.Combo("/comments").
  776. Get(repo.GetPullReviewComments)
  777. })
  778. })
  779. m.Combo("/requested_reviewers").
  780. Delete(reqToken(), bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
  781. Post(reqToken(), bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
  782. })
  783. }, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false))
  784. m.Group("/statuses", func() {
  785. m.Combo("/:sha").Get(repo.GetCommitStatuses).
  786. Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
  787. }, reqRepoReader(models.UnitTypeCode))
  788. m.Group("/commits", func() {
  789. m.Get("", repo.GetAllCommits)
  790. m.Group("/:ref", func() {
  791. m.Get("/status", repo.GetCombinedCommitStatusByRef)
  792. m.Get("/statuses", repo.GetCommitStatusesByRef)
  793. })
  794. }, reqRepoReader(models.UnitTypeCode))
  795. m.Group("/git", func() {
  796. m.Group("/commits", func() {
  797. m.Get("/:sha", repo.GetSingleCommit)
  798. })
  799. m.Get("/refs", repo.GetGitAllRefs)
  800. m.Get("/refs/*", repo.GetGitRefs)
  801. m.Get("/trees/:sha", context.RepoRefForAPI(), repo.GetTree)
  802. m.Get("/blobs/:sha", context.RepoRefForAPI(), repo.GetBlob)
  803. m.Get("/tags/:sha", context.RepoRefForAPI(), repo.GetTag)
  804. }, reqRepoReader(models.UnitTypeCode))
  805. m.Group("/contents", func() {
  806. m.Get("", repo.GetContentsList)
  807. m.Get("/*", repo.GetContents)
  808. m.Group("/*", func() {
  809. m.Post("", bind(api.CreateFileOptions{}), repo.CreateFile)
  810. m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile)
  811. m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile)
  812. }, reqRepoWriter(models.UnitTypeCode), reqToken())
  813. }, reqRepoReader(models.UnitTypeCode))
  814. m.Get("/signing-key.gpg", misc.SigningKey)
  815. m.Group("/topics", func() {
  816. m.Combo("").Get(repo.ListTopics).
  817. Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
  818. m.Group("/:topic", func() {
  819. m.Combo("").Put(reqToken(), repo.AddTopic).
  820. Delete(reqToken(), repo.DeleteTopic)
  821. }, reqAdmin())
  822. }, reqAnyRepoReader())
  823. m.Get("/issue_templates", context.ReferencesGitRepo(false), repo.GetIssueTemplates)
  824. m.Get("/languages", reqRepoReader(models.UnitTypeCode), repo.GetLanguages)
  825. }, repoAssignment())
  826. })
  827. // Organizations
  828. m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
  829. m.Get("/users/:username/orgs", org.ListUserOrgs)
  830. m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create)
  831. m.Get("/orgs", org.GetAll)
  832. m.Group("/orgs/:org", func() {
  833. m.Combo("").Get(org.Get).
  834. Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
  835. Delete(reqToken(), reqOrgOwnership(), org.Delete)
  836. m.Combo("/repos").Get(user.ListOrgRepos).
  837. Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
  838. m.Group("/members", func() {
  839. m.Get("", org.ListMembers)
  840. m.Combo("/:username").Get(org.IsMember).
  841. Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
  842. })
  843. m.Group("/public_members", func() {
  844. m.Get("", org.ListPublicMembers)
  845. m.Combo("/:username").Get(org.IsPublicMember).
  846. Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
  847. Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
  848. })
  849. m.Group("/teams", func() {
  850. m.Combo("", reqToken()).Get(org.ListTeams).
  851. Post(reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
  852. m.Get("/search", org.SearchTeam)
  853. }, reqOrgMembership())
  854. m.Group("/labels", func() {
  855. m.Get("", org.ListLabels)
  856. m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
  857. m.Combo("/:id").Get(org.GetLabel).
  858. Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
  859. Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
  860. })
  861. m.Group("/hooks", func() {
  862. m.Combo("").Get(org.ListHooks).
  863. Post(bind(api.CreateHookOption{}), org.CreateHook)
  864. m.Combo("/:id").Get(org.GetHook).
  865. Patch(bind(api.EditHookOption{}), org.EditHook).
  866. Delete(org.DeleteHook)
  867. }, reqToken(), reqOrgOwnership())
  868. }, orgAssignment(true))
  869. m.Group("/teams/:teamid", func() {
  870. m.Combo("").Get(org.GetTeam).
  871. Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
  872. Delete(reqOrgOwnership(), org.DeleteTeam)
  873. m.Group("/members", func() {
  874. m.Get("", org.GetTeamMembers)
  875. m.Combo("/:username").
  876. Get(org.GetTeamMember).
  877. Put(reqOrgOwnership(), org.AddTeamMember).
  878. Delete(reqOrgOwnership(), org.RemoveTeamMember)
  879. })
  880. m.Group("/repos", func() {
  881. m.Get("", org.GetTeamRepos)
  882. m.Combo("/:org/:reponame").
  883. Put(org.AddTeamRepository).
  884. Delete(org.RemoveTeamRepository)
  885. })
  886. }, orgAssignment(false, true), reqToken(), reqTeamMembership())
  887. m.Any("/*", func(ctx *context.APIContext) {
  888. ctx.NotFound()
  889. })
  890. m.Group("/admin", func() {
  891. m.Group("/cron", func() {
  892. m.Get("", admin.ListCronTasks)
  893. m.Post("/:task", admin.PostCronTask)
  894. })
  895. m.Get("/orgs", admin.GetAllOrgs)
  896. m.Group("/users", func() {
  897. m.Get("", admin.GetAllUsers)
  898. m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
  899. m.Group("/:username", func() {
  900. m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
  901. Delete(admin.DeleteUser)
  902. m.Group("/keys", func() {
  903. m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
  904. m.Delete("/:id", admin.DeleteUserPublicKey)
  905. })
  906. m.Get("/orgs", org.ListUserOrgs)
  907. m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
  908. m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
  909. })
  910. })
  911. m.Group("/unadopted", func() {
  912. m.Get("", admin.ListUnadoptedRepositories)
  913. m.Post("/:username/:reponame", admin.AdoptRepository)
  914. m.Delete("/:username/:reponame", admin.DeleteUnadoptedRepository)
  915. })
  916. }, reqToken(), reqSiteAdmin())
  917. m.Group("/topics", func() {
  918. m.Get("/search", repo.TopicSearch)
  919. })
  920. }, securityHeaders(), context.APIContexter(), sudo())
  921. }
  922. func securityHeaders() macaron.Handler {
  923. return func(ctx *macaron.Context) {
  924. ctx.Resp.Before(func(w macaron.ResponseWriter) {
  925. // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
  926. // http://stackoverflow.com/a/3146618/244009
  927. w.Header().Set("x-content-type-options", "nosniff")
  928. })
  929. }
  930. }