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

api.go 15 kB

Add basic integration test infrastructure (and new endpoint `/api/v1/version` for testing it) (#741) * Implement '/api/v1/version' * Cleanup and various fixes * Enhance run.sh * Add install_test.go * Add parameter utils.Config for testing handlers * Re-organize TestVersion.go * Rename functions * handling process cleanup properly * Fix missing function renaming * Cleanup the 'retry' logic * Cleanup * Remove unneeded logging code * Logging messages tweaking * Logging message tweaking * Fix logging messages * Use 'const' instead of hardwired numbers * We don't really need retries anymore * Move constant ServerHttpPort to install_test.go * Restore mistakenly removed constant * Add required comments to make the linter happy. * Fix comments and naming to address linter's complaints * Detect Gitea executale version automatically * Remove tests/run.sh, `go test` suffices. * Make `make build` a prerequisite of `make test` * Do not sleep before trying * Speedup the server pinging loop * Use defined const instead of hardwired numbers * Remove redundant error handling * Use a dedicated target for running code.gitea.io/tests * Do not make 'test' depend on 'build' target * Rectify the excluded package list * Remove redundant 'exit 1' * Change the API to allow passing test.T to test handlers * Make testing.T an embedded field * Use assert.Equal to comparing results * Add copyright info * Parametrized logging output * Use tmpdir instead * Eliminate redundant casting * Remove unneeded variable * Fix last commit * Add missing copyright info * Replace fmt.Fprintf with fmt.Fprint * rename the xtest to integration-test * Use Symlink instead of hard-link for cross-device linking * Turn debugging logs on * Follow the existing framework for APIs * Output logs only if test.v is true * Re-order import statements * Enhance the error message * Fix comment which breaks the linter's rule * Rename 'integration-test' to 'e2e-test' for saving keystrokes * Add comment to avoid possible confusion * Rename tests -> integration-tests Also change back the Makefile to use `make integration-test`. * Use tests/integration for now * tests/integration -> integrations Slightly flattened directory hierarchy is better. * Update Makefile accordingly * Fix a missing change in Makefile * govendor update code.gitea.io/sdk/gitea * Fix comment of struct fields * Fix conditional nonsense * Fix missing updates regarding version string changes * Make variable naming more consistent * Check http status code * Rectify error messages
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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. //go:generate swagger generate spec -o ../../../public/swagger.v1.json
  5. //go:generate sed -i "s;\".ref\": \"#/definitions/GPGKey\";\"type\": \"object\";g" ../../../public/swagger.v1.json
  6. // Package v1 Gitea API.
  7. //
  8. // This provide API interface to communicate with this Gitea instance.
  9. //
  10. // Terms Of Service:
  11. //
  12. // there are no TOS at this moment, use at your own risk we take no responsibility
  13. //
  14. // Schemes: http, https
  15. // BasePath: /api/v1
  16. // Version: 1.1.1
  17. // License: MIT http://opensource.org/licenses/MIT
  18. //
  19. // Consumes:
  20. // - application/json
  21. // - text/plain
  22. //
  23. // Produces:
  24. // - application/json
  25. // - text/html
  26. //
  27. // swagger:meta
  28. package v1
  29. import (
  30. "strings"
  31. "github.com/go-macaron/binding"
  32. "gopkg.in/macaron.v1"
  33. api "code.gitea.io/sdk/gitea"
  34. "code.gitea.io/gitea/models"
  35. "code.gitea.io/gitea/modules/auth"
  36. "code.gitea.io/gitea/modules/context"
  37. "code.gitea.io/gitea/routers/api/v1/admin"
  38. "code.gitea.io/gitea/routers/api/v1/misc"
  39. "code.gitea.io/gitea/routers/api/v1/org"
  40. "code.gitea.io/gitea/routers/api/v1/repo"
  41. "code.gitea.io/gitea/routers/api/v1/user"
  42. )
  43. func repoAssignment() macaron.Handler {
  44. return func(ctx *context.APIContext) {
  45. userName := ctx.Params(":username")
  46. repoName := ctx.Params(":reponame")
  47. var (
  48. owner *models.User
  49. err error
  50. )
  51. // Check if the user is the same as the repository owner.
  52. if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
  53. owner = ctx.User
  54. } else {
  55. owner, err = models.GetUserByName(userName)
  56. if err != nil {
  57. if models.IsErrUserNotExist(err) {
  58. ctx.Status(404)
  59. } else {
  60. ctx.Error(500, "GetUserByName", err)
  61. }
  62. return
  63. }
  64. }
  65. ctx.Repo.Owner = owner
  66. // Get repository.
  67. repo, err := models.GetRepositoryByName(owner.ID, repoName)
  68. if err != nil {
  69. if models.IsErrRepoNotExist(err) {
  70. redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
  71. if err == nil {
  72. context.RedirectToRepo(ctx.Context, redirectRepoID)
  73. } else if models.IsErrRepoRedirectNotExist(err) {
  74. ctx.Status(404)
  75. } else {
  76. ctx.Error(500, "LookupRepoRedirect", err)
  77. }
  78. } else {
  79. ctx.Error(500, "GetRepositoryByName", err)
  80. }
  81. return
  82. }
  83. repo.Owner = owner
  84. if ctx.IsSigned && ctx.User.IsAdmin {
  85. ctx.Repo.AccessMode = models.AccessModeOwner
  86. } else {
  87. mode, err := models.AccessLevel(ctx.User.ID, repo)
  88. if err != nil {
  89. ctx.Error(500, "AccessLevel", err)
  90. return
  91. }
  92. ctx.Repo.AccessMode = mode
  93. }
  94. if !ctx.Repo.HasAccess() {
  95. ctx.Status(404)
  96. return
  97. }
  98. ctx.Repo.Repository = repo
  99. }
  100. }
  101. // Contexter middleware already checks token for user sign in process.
  102. func reqToken() macaron.Handler {
  103. return func(ctx *context.Context) {
  104. if !ctx.IsSigned {
  105. ctx.Error(401)
  106. return
  107. }
  108. }
  109. }
  110. func reqBasicAuth() macaron.Handler {
  111. return func(ctx *context.Context) {
  112. if !ctx.IsBasicAuth {
  113. ctx.Error(401)
  114. return
  115. }
  116. }
  117. }
  118. func reqAdmin() macaron.Handler {
  119. return func(ctx *context.Context) {
  120. if !ctx.IsSigned || !ctx.User.IsAdmin {
  121. ctx.Error(403)
  122. return
  123. }
  124. }
  125. }
  126. func reqRepoWriter() macaron.Handler {
  127. return func(ctx *context.Context) {
  128. if !ctx.Repo.IsWriter() {
  129. ctx.Error(403)
  130. return
  131. }
  132. }
  133. }
  134. func reqOrgMembership() macaron.Handler {
  135. return func(ctx *context.APIContext) {
  136. var orgID int64
  137. if ctx.Org.Organization != nil {
  138. orgID = ctx.Org.Organization.ID
  139. } else if ctx.Org.Team != nil {
  140. orgID = ctx.Org.Team.OrgID
  141. } else {
  142. ctx.Error(500, "", "reqOrgMembership: unprepared context")
  143. return
  144. }
  145. if !models.IsOrganizationMember(orgID, ctx.User.ID) {
  146. if ctx.Org.Organization != nil {
  147. ctx.Error(403, "", "Must be an organization member")
  148. } else {
  149. ctx.Status(404)
  150. }
  151. return
  152. }
  153. }
  154. }
  155. func reqOrgOwnership() macaron.Handler {
  156. return func(ctx *context.APIContext) {
  157. var orgID int64
  158. if ctx.Org.Organization != nil {
  159. orgID = ctx.Org.Organization.ID
  160. } else if ctx.Org.Team != nil {
  161. orgID = ctx.Org.Team.OrgID
  162. } else {
  163. ctx.Error(500, "", "reqOrgOwnership: unprepared context")
  164. return
  165. }
  166. if !models.IsOrganizationOwner(orgID, ctx.User.ID) {
  167. if ctx.Org.Organization != nil {
  168. ctx.Error(403, "", "Must be an organization owner")
  169. } else {
  170. ctx.Status(404)
  171. }
  172. return
  173. }
  174. }
  175. }
  176. func orgAssignment(args ...bool) macaron.Handler {
  177. var (
  178. assignOrg bool
  179. assignTeam bool
  180. )
  181. if len(args) > 0 {
  182. assignOrg = args[0]
  183. }
  184. if len(args) > 1 {
  185. assignTeam = args[1]
  186. }
  187. return func(ctx *context.APIContext) {
  188. ctx.Org = new(context.APIOrganization)
  189. var err error
  190. if assignOrg {
  191. ctx.Org.Organization, err = models.GetUserByName(ctx.Params(":orgname"))
  192. if err != nil {
  193. if models.IsErrUserNotExist(err) {
  194. ctx.Status(404)
  195. } else {
  196. ctx.Error(500, "GetUserByName", err)
  197. }
  198. return
  199. }
  200. }
  201. if assignTeam {
  202. ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid"))
  203. if err != nil {
  204. if models.IsErrUserNotExist(err) {
  205. ctx.Status(404)
  206. } else {
  207. ctx.Error(500, "GetTeamById", err)
  208. }
  209. return
  210. }
  211. }
  212. }
  213. }
  214. func mustEnableIssues(ctx *context.APIContext) {
  215. if !ctx.Repo.Repository.EnableUnit(models.UnitTypeIssues) {
  216. ctx.Status(404)
  217. return
  218. }
  219. }
  220. func mustAllowPulls(ctx *context.Context) {
  221. if !ctx.Repo.Repository.AllowsPulls() {
  222. ctx.Status(404)
  223. return
  224. }
  225. }
  226. // RegisterRoutes registers all v1 APIs routes to web application.
  227. // FIXME: custom form error response
  228. func RegisterRoutes(m *macaron.Macaron) {
  229. bind := binding.Bind
  230. m.Group("/v1", func() {
  231. // Miscellaneous
  232. m.Get("/version", misc.Version)
  233. m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
  234. m.Post("/markdown/raw", misc.MarkdownRaw)
  235. // Users
  236. m.Group("/users", func() {
  237. m.Get("/search", user.Search)
  238. m.Group("/:username", func() {
  239. m.Get("", user.GetInfo)
  240. m.Get("/repos", user.ListUserRepos)
  241. m.Group("/tokens", func() {
  242. m.Combo("").Get(user.ListAccessTokens).
  243. Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
  244. }, reqBasicAuth())
  245. })
  246. })
  247. m.Group("/users", func() {
  248. m.Group("/:username", func() {
  249. m.Get("/keys", user.ListPublicKeys)
  250. m.Get("/gpg_keys", user.ListGPGKeys)
  251. m.Get("/followers", user.ListFollowers)
  252. m.Group("/following", func() {
  253. m.Get("", user.ListFollowing)
  254. m.Get("/:target", user.CheckFollowing)
  255. })
  256. m.Get("/starred", user.GetStarredRepos)
  257. m.Get("/subscriptions", user.GetWatchedRepos)
  258. })
  259. }, reqToken())
  260. m.Group("/user", func() {
  261. m.Get("", user.GetAuthenticatedUser)
  262. m.Combo("/emails").Get(user.ListEmails).
  263. Post(bind(api.CreateEmailOption{}), user.AddEmail).
  264. Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)
  265. m.Get("/followers", user.ListMyFollowers)
  266. m.Group("/following", func() {
  267. m.Get("", user.ListMyFollowing)
  268. m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
  269. })
  270. m.Group("/keys", func() {
  271. m.Combo("").Get(user.ListMyPublicKeys).
  272. Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
  273. m.Combo("/:id").Get(user.GetPublicKey).
  274. Delete(user.DeletePublicKey)
  275. })
  276. m.Group("/gpg_keys", func() {
  277. m.Combo("").Get(user.ListMyGPGKeys).
  278. Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
  279. m.Combo("/:id").Get(user.GetGPGKey).
  280. Delete(user.DeleteGPGKey)
  281. })
  282. m.Combo("/repos").Get(user.ListMyRepos).
  283. Post(bind(api.CreateRepoOption{}), repo.Create)
  284. m.Group("/starred", func() {
  285. m.Get("", user.GetMyStarredRepos)
  286. m.Group("/:username/:reponame", func() {
  287. m.Get("", user.IsStarring)
  288. m.Put("", user.Star)
  289. m.Delete("", user.Unstar)
  290. }, repoAssignment())
  291. })
  292. m.Get("/subscriptions", user.GetMyWatchedRepos)
  293. }, reqToken())
  294. // Repositories
  295. m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
  296. m.Group("/repos", func() {
  297. m.Get("/search", repo.Search)
  298. })
  299. m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)
  300. m.Group("/repos", func() {
  301. m.Post("/migrate", bind(auth.MigrateRepoForm{}), repo.Migrate)
  302. m.Group("/:username/:reponame", func() {
  303. m.Combo("").Get(repo.Get).Delete(repo.Delete)
  304. m.Group("/hooks", func() {
  305. m.Combo("").Get(repo.ListHooks).
  306. Post(bind(api.CreateHookOption{}), repo.CreateHook)
  307. m.Combo("/:id").Get(repo.GetHook).
  308. Patch(bind(api.EditHookOption{}), repo.EditHook).
  309. Delete(repo.DeleteHook)
  310. }, reqRepoWriter())
  311. m.Group("/collaborators", func() {
  312. m.Get("", repo.ListCollaborators)
  313. m.Combo("/:collaborator").Get(repo.IsCollaborator).
  314. Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
  315. Delete(repo.DeleteCollaborator)
  316. })
  317. m.Get("/raw/*", context.RepoRef(), repo.GetRawFile)
  318. m.Get("/archive/*", repo.GetArchive)
  319. m.Combo("/forks").Get(repo.ListForks).
  320. Post(bind(api.CreateForkOption{}), repo.CreateFork)
  321. m.Group("/branches", func() {
  322. m.Get("", repo.ListBranches)
  323. m.Get("/:branchname", repo.GetBranch)
  324. })
  325. m.Group("/keys", func() {
  326. m.Combo("").Get(repo.ListDeployKeys).
  327. Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
  328. m.Combo("/:id").Get(repo.GetDeployKey).
  329. Delete(repo.DeleteDeploykey)
  330. })
  331. m.Group("/issues", func() {
  332. m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
  333. m.Group("/comments", func() {
  334. m.Get("", repo.ListRepoIssueComments)
  335. m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment)
  336. })
  337. m.Group("/:index", func() {
  338. m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)
  339. m.Group("/comments", func() {
  340. m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
  341. m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
  342. Delete(repo.DeleteIssueComment)
  343. })
  344. m.Group("/labels", func() {
  345. m.Combo("").Get(repo.ListIssueLabels).
  346. Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
  347. Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
  348. Delete(repo.ClearIssueLabels)
  349. m.Delete("/:id", repo.DeleteIssueLabel)
  350. })
  351. })
  352. }, mustEnableIssues)
  353. m.Group("/labels", func() {
  354. m.Combo("").Get(repo.ListLabels).
  355. Post(bind(api.CreateLabelOption{}), repo.CreateLabel)
  356. m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel).
  357. Delete(repo.DeleteLabel)
  358. })
  359. m.Group("/milestones", func() {
  360. m.Combo("").Get(repo.ListMilestones).
  361. Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
  362. m.Combo("/:id").Get(repo.GetMilestone).
  363. Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone).
  364. Delete(reqRepoWriter(), repo.DeleteMilestone)
  365. })
  366. m.Get("/stargazers", repo.ListStargazers)
  367. m.Get("/subscribers", repo.ListSubscribers)
  368. m.Group("/subscription", func() {
  369. m.Get("", user.IsWatching)
  370. m.Put("", user.Watch)
  371. m.Delete("", user.Unwatch)
  372. })
  373. m.Group("/releases", func() {
  374. m.Combo("").Get(repo.ListReleases).
  375. Post(bind(api.CreateReleaseOption{}), repo.CreateRelease)
  376. m.Combo("/:id").Get(repo.GetRelease).
  377. Patch(bind(api.EditReleaseOption{}), repo.EditRelease).
  378. Delete(repo.DeleteRelease)
  379. })
  380. m.Post("/mirror-sync", repo.MirrorSync)
  381. m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig)
  382. m.Group("/pulls", func() {
  383. m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).Post(reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
  384. m.Group("/:index", func() {
  385. m.Combo("").Get(repo.GetPullRequest).Patch(reqRepoWriter(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
  386. m.Combo("/merge").Get(repo.IsPullRequestMerged).Post(reqRepoWriter(), repo.MergePullRequest)
  387. })
  388. }, mustAllowPulls, context.ReferencesGitRepo())
  389. m.Group("/statuses", func() {
  390. m.Combo("/:sha").Get(repo.GetCommitStatuses).Post(reqRepoWriter(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
  391. })
  392. m.Group("/commits/:ref", func() {
  393. m.Get("/status", repo.GetCombinedCommitStatus)
  394. m.Get("/statuses", repo.GetCommitStatuses)
  395. })
  396. }, repoAssignment())
  397. }, reqToken())
  398. // Organizations
  399. m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
  400. m.Get("/users/:username/orgs", org.ListUserOrgs)
  401. m.Group("/orgs/:orgname", func() {
  402. m.Combo("").Get(org.Get).
  403. Patch(reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit)
  404. m.Group("/members", func() {
  405. m.Get("", org.ListMembers)
  406. m.Combo("/:username").Get(org.IsMember).
  407. Delete(reqOrgOwnership(), org.DeleteMember)
  408. })
  409. m.Group("/public_members", func() {
  410. m.Get("", org.ListPublicMembers)
  411. m.Combo("/:username").Get(org.IsPublicMember).
  412. Put(reqOrgMembership(), org.PublicizeMember).
  413. Delete(reqOrgMembership(), org.ConcealMember)
  414. })
  415. m.Combo("/teams", reqOrgMembership()).Get(org.ListTeams).
  416. Post(bind(api.CreateTeamOption{}), org.CreateTeam)
  417. m.Group("/hooks", func() {
  418. m.Combo("").Get(org.ListHooks).
  419. Post(bind(api.CreateHookOption{}), org.CreateHook)
  420. m.Combo("/:id").Get(org.GetHook).
  421. Patch(reqOrgOwnership(), bind(api.EditHookOption{}), org.EditHook).
  422. Delete(reqOrgOwnership(), org.DeleteHook)
  423. }, reqOrgMembership())
  424. }, orgAssignment(true))
  425. m.Group("/teams/:teamid", func() {
  426. m.Combo("").Get(org.GetTeam).
  427. Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
  428. Delete(reqOrgOwnership(), org.DeleteTeam)
  429. m.Group("/members", func() {
  430. m.Get("", org.GetTeamMembers)
  431. m.Combo("/:username").
  432. Put(reqOrgOwnership(), org.AddTeamMember).
  433. Delete(reqOrgOwnership(), org.RemoveTeamMember)
  434. })
  435. m.Group("/repos", func() {
  436. m.Get("", org.GetTeamRepos)
  437. m.Combo(":orgname/:reponame").
  438. Put(org.AddTeamRepository).
  439. Delete(org.RemoveTeamRepository)
  440. })
  441. }, reqOrgMembership(), orgAssignment(false, true))
  442. m.Any("/*", func(ctx *context.Context) {
  443. ctx.Error(404)
  444. })
  445. m.Group("/admin", func() {
  446. m.Group("/users", func() {
  447. m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
  448. m.Group("/:username", func() {
  449. m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
  450. Delete(admin.DeleteUser)
  451. m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
  452. m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
  453. m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
  454. })
  455. })
  456. }, reqAdmin())
  457. }, context.APIContexter())
  458. }