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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. // Package v1 Gitea API.
  5. //
  6. // This documentation describes the Gitea API.
  7. //
  8. // Schemes: http, https
  9. // BasePath: /api/v1
  10. // Version: 1.1.1
  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. //
  27. // SecurityDefinitions:
  28. // BasicAuth:
  29. // type: basic
  30. // Token:
  31. // type: apiKey
  32. // name: token
  33. // in: query
  34. // AccessToken:
  35. // type: apiKey
  36. // name: access_token
  37. // in: query
  38. // AuthorizationHeaderToken:
  39. // type: apiKey
  40. // name: Authorization
  41. // in: header
  42. //
  43. // swagger:meta
  44. package v1
  45. import (
  46. "strings"
  47. "code.gitea.io/gitea/models"
  48. "code.gitea.io/gitea/modules/auth"
  49. "code.gitea.io/gitea/modules/context"
  50. "code.gitea.io/gitea/routers/api/v1/admin"
  51. "code.gitea.io/gitea/routers/api/v1/misc"
  52. "code.gitea.io/gitea/routers/api/v1/org"
  53. "code.gitea.io/gitea/routers/api/v1/repo"
  54. _ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
  55. "code.gitea.io/gitea/routers/api/v1/user"
  56. "code.gitea.io/gitea/routers/api/v1/utils"
  57. api "code.gitea.io/sdk/gitea"
  58. "github.com/go-macaron/binding"
  59. "gopkg.in/macaron.v1"
  60. )
  61. func repoAssignment() macaron.Handler {
  62. return func(ctx *context.APIContext) {
  63. userName := ctx.Params(":username")
  64. repoName := ctx.Params(":reponame")
  65. var (
  66. owner *models.User
  67. err error
  68. )
  69. // Check if the user is the same as the repository owner.
  70. if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
  71. owner = ctx.User
  72. } else {
  73. owner, err = models.GetUserByName(userName)
  74. if err != nil {
  75. if models.IsErrUserNotExist(err) {
  76. ctx.Status(404)
  77. } else {
  78. ctx.Error(500, "GetUserByName", err)
  79. }
  80. return
  81. }
  82. }
  83. ctx.Repo.Owner = owner
  84. // Get repository.
  85. repo, err := models.GetRepositoryByName(owner.ID, repoName)
  86. if err != nil {
  87. if models.IsErrRepoNotExist(err) {
  88. redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
  89. if err == nil {
  90. context.RedirectToRepo(ctx.Context, redirectRepoID)
  91. } else if models.IsErrRepoRedirectNotExist(err) {
  92. ctx.Status(404)
  93. } else {
  94. ctx.Error(500, "LookupRepoRedirect", err)
  95. }
  96. } else {
  97. ctx.Error(500, "GetRepositoryByName", err)
  98. }
  99. return
  100. }
  101. repo.Owner = owner
  102. if ctx.IsSigned && ctx.User.IsAdmin {
  103. ctx.Repo.AccessMode = models.AccessModeOwner
  104. } else {
  105. mode, err := models.AccessLevel(utils.UserID(ctx), repo)
  106. if err != nil {
  107. ctx.Error(500, "AccessLevel", err)
  108. return
  109. }
  110. ctx.Repo.AccessMode = mode
  111. }
  112. if !ctx.Repo.HasAccess() {
  113. ctx.Status(404)
  114. return
  115. }
  116. ctx.Repo.Repository = repo
  117. }
  118. }
  119. // Contexter middleware already checks token for user sign in process.
  120. func reqToken() macaron.Handler {
  121. return func(ctx *context.Context) {
  122. if !ctx.IsSigned {
  123. ctx.Error(401)
  124. return
  125. }
  126. }
  127. }
  128. func reqBasicAuth() macaron.Handler {
  129. return func(ctx *context.Context) {
  130. if !ctx.IsBasicAuth {
  131. ctx.Error(401)
  132. return
  133. }
  134. }
  135. }
  136. func reqAdmin() macaron.Handler {
  137. return func(ctx *context.Context) {
  138. if !ctx.IsSigned || !ctx.User.IsAdmin {
  139. ctx.Error(403)
  140. return
  141. }
  142. }
  143. }
  144. func reqRepoWriter() macaron.Handler {
  145. return func(ctx *context.Context) {
  146. if !ctx.Repo.IsWriter() {
  147. ctx.Error(403)
  148. return
  149. }
  150. }
  151. }
  152. func reqOrgMembership() macaron.Handler {
  153. return func(ctx *context.APIContext) {
  154. var orgID int64
  155. if ctx.Org.Organization != nil {
  156. orgID = ctx.Org.Organization.ID
  157. } else if ctx.Org.Team != nil {
  158. orgID = ctx.Org.Team.OrgID
  159. } else {
  160. ctx.Error(500, "", "reqOrgMembership: unprepared context")
  161. return
  162. }
  163. if isMember, err := models.IsOrganizationMember(orgID, ctx.User.ID); err != nil {
  164. ctx.Error(500, "IsOrganizationMember", err)
  165. return
  166. } else if !isMember {
  167. if ctx.Org.Organization != nil {
  168. ctx.Error(403, "", "Must be an organization member")
  169. } else {
  170. ctx.Status(404)
  171. }
  172. return
  173. }
  174. }
  175. }
  176. func reqOrgOwnership() macaron.Handler {
  177. return func(ctx *context.APIContext) {
  178. var orgID int64
  179. if ctx.Org.Organization != nil {
  180. orgID = ctx.Org.Organization.ID
  181. } else if ctx.Org.Team != nil {
  182. orgID = ctx.Org.Team.OrgID
  183. } else {
  184. ctx.Error(500, "", "reqOrgOwnership: unprepared context")
  185. return
  186. }
  187. isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID)
  188. if err != nil {
  189. ctx.Error(500, "IsOrganizationOwner", err)
  190. } else if !isOwner {
  191. if ctx.Org.Organization != nil {
  192. ctx.Error(403, "", "Must be an organization owner")
  193. } else {
  194. ctx.Status(404)
  195. }
  196. return
  197. }
  198. }
  199. }
  200. func orgAssignment(args ...bool) macaron.Handler {
  201. var (
  202. assignOrg bool
  203. assignTeam bool
  204. )
  205. if len(args) > 0 {
  206. assignOrg = args[0]
  207. }
  208. if len(args) > 1 {
  209. assignTeam = args[1]
  210. }
  211. return func(ctx *context.APIContext) {
  212. ctx.Org = new(context.APIOrganization)
  213. var err error
  214. if assignOrg {
  215. ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":orgname"))
  216. if err != nil {
  217. if models.IsErrOrgNotExist(err) {
  218. ctx.Status(404)
  219. } else {
  220. ctx.Error(500, "GetOrgByName", err)
  221. }
  222. return
  223. }
  224. }
  225. if assignTeam {
  226. ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid"))
  227. if err != nil {
  228. if models.IsErrUserNotExist(err) {
  229. ctx.Status(404)
  230. } else {
  231. ctx.Error(500, "GetTeamById", err)
  232. }
  233. return
  234. }
  235. }
  236. }
  237. }
  238. func mustEnableIssues(ctx *context.APIContext) {
  239. if !ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) {
  240. ctx.Status(404)
  241. return
  242. }
  243. }
  244. func mustAllowPulls(ctx *context.Context) {
  245. if !ctx.Repo.Repository.AllowsPulls() {
  246. ctx.Status(404)
  247. return
  248. }
  249. }
  250. // RegisterRoutes registers all v1 APIs routes to web application.
  251. // FIXME: custom form error response
  252. func RegisterRoutes(m *macaron.Macaron) {
  253. bind := binding.Bind
  254. m.Get("/swagger", misc.Swagger) //Render V1 by default
  255. m.Group("/v1", func() {
  256. // Miscellaneous
  257. m.Get("/swagger", misc.Swagger)
  258. m.Get("/version", misc.Version)
  259. m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
  260. m.Post("/markdown/raw", misc.MarkdownRaw)
  261. // Users
  262. m.Group("/users", func() {
  263. m.Get("/search", user.Search)
  264. m.Group("/:username", func() {
  265. m.Get("", user.GetInfo)
  266. m.Get("/repos", user.ListUserRepos)
  267. m.Group("/tokens", func() {
  268. m.Combo("").Get(user.ListAccessTokens).
  269. Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
  270. }, reqBasicAuth())
  271. })
  272. })
  273. m.Group("/users", func() {
  274. m.Group("/:username", func() {
  275. m.Get("/keys", user.ListPublicKeys)
  276. m.Get("/gpg_keys", user.ListGPGKeys)
  277. m.Get("/followers", user.ListFollowers)
  278. m.Group("/following", func() {
  279. m.Get("", user.ListFollowing)
  280. m.Get("/:target", user.CheckFollowing)
  281. })
  282. m.Get("/starred", user.GetStarredRepos)
  283. m.Get("/subscriptions", user.GetWatchedRepos)
  284. })
  285. }, reqToken())
  286. m.Group("/user", func() {
  287. m.Get("", user.GetAuthenticatedUser)
  288. m.Combo("/emails").Get(user.ListEmails).
  289. Post(bind(api.CreateEmailOption{}), user.AddEmail).
  290. Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
  291. m.Get("/followers", user.ListMyFollowers)
  292. m.Group("/following", func() {
  293. m.Get("", user.ListMyFollowing)
  294. m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
  295. })
  296. m.Group("/keys", func() {
  297. m.Combo("").Get(user.ListMyPublicKeys).
  298. Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
  299. m.Combo("/:id").Get(user.GetPublicKey).
  300. Delete(user.DeletePublicKey)
  301. })
  302. m.Group("/gpg_keys", func() {
  303. m.Combo("").Get(user.ListMyGPGKeys).
  304. Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
  305. m.Combo("/:id").Get(user.GetGPGKey).
  306. Delete(user.DeleteGPGKey)
  307. })
  308. m.Combo("/repos").Get(user.ListMyRepos).
  309. Post(bind(api.CreateRepoOption{}), repo.Create)
  310. m.Group("/starred", func() {
  311. m.Get("", user.GetMyStarredRepos)
  312. m.Group("/:username/:reponame", func() {
  313. m.Get("", user.IsStarring)
  314. m.Put("", user.Star)
  315. m.Delete("", user.Unstar)
  316. }, repoAssignment())
  317. })
  318. m.Get("/times", repo.ListMyTrackedTimes)
  319. m.Get("/subscriptions", user.GetMyWatchedRepos)
  320. }, reqToken())
  321. // Repositories
  322. m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
  323. m.Group("/repos", func() {
  324. m.Get("/search", repo.Search)
  325. })
  326. m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)
  327. m.Group("/repos", func() {
  328. m.Post("/migrate", reqToken(), bind(auth.MigrateRepoForm{}), repo.Migrate)
  329. m.Group("/:username/:reponame", func() {
  330. m.Combo("").Get(repo.Get).Delete(reqToken(), repo.Delete)
  331. m.Group("/hooks", func() {
  332. m.Combo("").Get(repo.ListHooks).
  333. Post(bind(api.CreateHookOption{}), repo.CreateHook)
  334. m.Combo("/:id").Get(repo.GetHook).
  335. Patch(bind(api.EditHookOption{}), repo.EditHook).
  336. Delete(repo.DeleteHook)
  337. }, reqToken(), reqRepoWriter())
  338. m.Group("/collaborators", func() {
  339. m.Get("", repo.ListCollaborators)
  340. m.Combo("/:collaborator").Get(repo.IsCollaborator).
  341. Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
  342. Delete(repo.DeleteCollaborator)
  343. }, reqToken())
  344. m.Get("/raw/*", context.RepoRefByType(context.RepoRefAny), repo.GetRawFile)
  345. m.Get("/archive/*", repo.GetArchive)
  346. m.Combo("/forks").Get(repo.ListForks).
  347. Post(reqToken(), bind(api.CreateForkOption{}), repo.CreateFork)
  348. m.Group("/branches", func() {
  349. m.Get("", repo.ListBranches)
  350. m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
  351. })
  352. m.Group("/keys", func() {
  353. m.Combo("").Get(repo.ListDeployKeys).
  354. Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
  355. m.Combo("/:id").Get(repo.GetDeployKey).
  356. Delete(repo.DeleteDeploykey)
  357. }, reqToken(), reqRepoWriter())
  358. m.Group("/times", func() {
  359. m.Combo("").Get(repo.ListTrackedTimesByRepository)
  360. m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser)
  361. }, mustEnableIssues)
  362. m.Group("/issues", func() {
  363. m.Combo("").Get(repo.ListIssues).
  364. Post(reqToken(), bind(api.CreateIssueOption{}), repo.CreateIssue)
  365. m.Group("/comments", func() {
  366. m.Get("", repo.ListRepoIssueComments)
  367. m.Combo("/:id", reqToken()).
  368. Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
  369. Delete(repo.DeleteIssueComment)
  370. })
  371. m.Group("/:index", func() {
  372. m.Combo("").Get(repo.GetIssue).
  373. Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
  374. m.Group("/comments", func() {
  375. m.Combo("").Get(repo.ListIssueComments).
  376. Post(reqToken(), bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
  377. m.Combo("/:id", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
  378. Delete(repo.DeleteIssueCommentDeprecated)
  379. })
  380. m.Group("/labels", func() {
  381. m.Combo("").Get(repo.ListIssueLabels).
  382. Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
  383. Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
  384. Delete(reqToken(), repo.ClearIssueLabels)
  385. m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
  386. })
  387. m.Group("/times", func() {
  388. m.Combo("").Get(repo.ListTrackedTimes).
  389. Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
  390. })
  391. })
  392. }, mustEnableIssues)
  393. m.Group("/labels", func() {
  394. m.Combo("").Get(repo.ListLabels).
  395. Post(reqToken(), bind(api.CreateLabelOption{}), repo.CreateLabel)
  396. m.Combo("/:id").Get(repo.GetLabel).
  397. Patch(reqToken(), bind(api.EditLabelOption{}), repo.EditLabel).
  398. Delete(reqToken(), repo.DeleteLabel)
  399. })
  400. m.Group("/milestones", func() {
  401. m.Combo("").Get(repo.ListMilestones).
  402. Post(reqToken(), reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
  403. m.Combo("/:id").Get(repo.GetMilestone).
  404. Patch(reqToken(), reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone).
  405. Delete(reqToken(), reqRepoWriter(), repo.DeleteMilestone)
  406. })
  407. m.Get("/stargazers", repo.ListStargazers)
  408. m.Get("/subscribers", repo.ListSubscribers)
  409. m.Group("/subscription", func() {
  410. m.Get("", user.IsWatching)
  411. m.Put("", reqToken(), user.Watch)
  412. m.Delete("", reqToken(), user.Unwatch)
  413. })
  414. m.Group("/releases", func() {
  415. m.Combo("").Get(repo.ListReleases).
  416. Post(reqToken(), reqRepoWriter(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
  417. m.Combo("/:id").Get(repo.GetRelease).
  418. Patch(reqToken(), reqRepoWriter(), bind(api.EditReleaseOption{}), repo.EditRelease).
  419. Delete(reqToken(), reqRepoWriter(), repo.DeleteRelease)
  420. })
  421. m.Post("/mirror-sync", reqToken(), reqRepoWriter(), repo.MirrorSync)
  422. m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig)
  423. m.Group("/pulls", func() {
  424. m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).
  425. Post(reqToken(), reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
  426. m.Group("/:index", func() {
  427. m.Combo("").Get(repo.GetPullRequest).
  428. Patch(reqToken(), reqRepoWriter(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
  429. m.Combo("/merge").Get(repo.IsPullRequestMerged).
  430. Post(reqToken(), reqRepoWriter(), bind(auth.MergePullRequestForm{}), repo.MergePullRequest)
  431. })
  432. }, mustAllowPulls, context.ReferencesGitRepo())
  433. m.Group("/statuses", func() {
  434. m.Combo("/:sha").Get(repo.GetCommitStatuses).
  435. Post(reqToken(), reqRepoWriter(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
  436. })
  437. m.Group("/commits/:ref", func() {
  438. m.Get("/status", repo.GetCombinedCommitStatusByRef)
  439. m.Get("/statuses", repo.GetCommitStatusesByRef)
  440. })
  441. }, repoAssignment())
  442. })
  443. // Organizations
  444. m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
  445. m.Get("/users/:username/orgs", org.ListUserOrgs)
  446. m.Group("/orgs/:orgname", func() {
  447. m.Get("/repos", user.ListOrgRepos)
  448. m.Combo("").Get(org.Get).
  449. Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit)
  450. m.Group("/members", func() {
  451. m.Get("", org.ListMembers)
  452. m.Combo("/:username").Get(org.IsMember).
  453. Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
  454. })
  455. m.Group("/public_members", func() {
  456. m.Get("", org.ListPublicMembers)
  457. m.Combo("/:username").Get(org.IsPublicMember).
  458. Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
  459. Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
  460. })
  461. m.Combo("/teams", reqToken(), reqOrgMembership()).Get(org.ListTeams).
  462. Post(bind(api.CreateTeamOption{}), org.CreateTeam)
  463. m.Group("/hooks", func() {
  464. m.Combo("").Get(org.ListHooks).
  465. Post(bind(api.CreateHookOption{}), org.CreateHook)
  466. m.Combo("/:id").Get(org.GetHook).
  467. Patch(reqOrgOwnership(), bind(api.EditHookOption{}), org.EditHook).
  468. Delete(reqOrgOwnership(), org.DeleteHook)
  469. }, reqToken(), reqOrgMembership())
  470. }, orgAssignment(true))
  471. m.Group("/teams/:teamid", func() {
  472. m.Combo("").Get(org.GetTeam).
  473. Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
  474. Delete(reqOrgOwnership(), org.DeleteTeam)
  475. m.Group("/members", func() {
  476. m.Get("", org.GetTeamMembers)
  477. m.Combo("/:username").
  478. Put(reqOrgOwnership(), org.AddTeamMember).
  479. Delete(reqOrgOwnership(), org.RemoveTeamMember)
  480. })
  481. m.Group("/repos", func() {
  482. m.Get("", org.GetTeamRepos)
  483. m.Combo("/:orgname/:reponame").
  484. Put(org.AddTeamRepository).
  485. Delete(org.RemoveTeamRepository)
  486. })
  487. }, orgAssignment(false, true), reqToken(), reqOrgMembership())
  488. m.Any("/*", func(ctx *context.Context) {
  489. ctx.Error(404)
  490. })
  491. m.Group("/admin", func() {
  492. m.Group("/users", func() {
  493. m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
  494. m.Group("/:username", func() {
  495. m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
  496. Delete(admin.DeleteUser)
  497. m.Group("/keys", func() {
  498. m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
  499. m.Delete("/:id", admin.DeleteUserPublicKey)
  500. })
  501. m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
  502. m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
  503. })
  504. })
  505. }, reqAdmin())
  506. }, context.APIContexter())
  507. }