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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548
  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. "fmt"
  66. "net/http"
  67. "strings"
  68. actions_model "code.gitea.io/gitea/models/actions"
  69. auth_model "code.gitea.io/gitea/models/auth"
  70. "code.gitea.io/gitea/models/organization"
  71. "code.gitea.io/gitea/models/perm"
  72. access_model "code.gitea.io/gitea/models/perm/access"
  73. repo_model "code.gitea.io/gitea/models/repo"
  74. "code.gitea.io/gitea/models/unit"
  75. user_model "code.gitea.io/gitea/models/user"
  76. "code.gitea.io/gitea/modules/context"
  77. "code.gitea.io/gitea/modules/log"
  78. "code.gitea.io/gitea/modules/setting"
  79. api "code.gitea.io/gitea/modules/structs"
  80. "code.gitea.io/gitea/modules/web"
  81. "code.gitea.io/gitea/routers/api/v1/activitypub"
  82. "code.gitea.io/gitea/routers/api/v1/admin"
  83. "code.gitea.io/gitea/routers/api/v1/misc"
  84. "code.gitea.io/gitea/routers/api/v1/notify"
  85. "code.gitea.io/gitea/routers/api/v1/org"
  86. "code.gitea.io/gitea/routers/api/v1/packages"
  87. "code.gitea.io/gitea/routers/api/v1/repo"
  88. "code.gitea.io/gitea/routers/api/v1/settings"
  89. "code.gitea.io/gitea/routers/api/v1/user"
  90. "code.gitea.io/gitea/routers/common"
  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(ctx, userName); err == nil {
  142. context.RedirectToUser(ctx.Base, 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.Base, 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. // if a token is being used for auth, we check that it contains the required scope
  222. // if a token is not being used, reqToken will enforce other sign in methods
  223. func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
  224. return func(ctx *context.APIContext) {
  225. // no scope required
  226. if len(requiredScopeCategories) == 0 {
  227. return
  228. }
  229. // Need OAuth2 token to be present.
  230. scope, scopeExists := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
  231. if ctx.Data["IsApiToken"] != true || !scopeExists {
  232. return
  233. }
  234. ctx.Data["ApiTokenScopePublicRepoOnly"] = false
  235. ctx.Data["ApiTokenScopePublicOrgOnly"] = false
  236. // use the http method to determine the access level
  237. requiredScopeLevel := auth_model.Read
  238. if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
  239. requiredScopeLevel = auth_model.Write
  240. }
  241. // get the required scope for the given access level and category
  242. requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
  243. // check if scope only applies to public resources
  244. publicOnly, err := scope.PublicOnly()
  245. if err != nil {
  246. ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
  247. return
  248. }
  249. // this context is used by the middleware in the specific route
  250. ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
  251. ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization)
  252. allow, err := scope.HasScope(requiredScopes...)
  253. if err != nil {
  254. ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
  255. return
  256. }
  257. if allow {
  258. return
  259. }
  260. ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
  261. }
  262. }
  263. // Contexter middleware already checks token for user sign in process.
  264. func reqToken() func(ctx *context.APIContext) {
  265. return func(ctx *context.APIContext) {
  266. // If actions token is present
  267. if true == ctx.Data["IsActionsToken"] {
  268. return
  269. }
  270. if true == ctx.Data["IsApiToken"] {
  271. publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"]
  272. publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"]
  273. if pubRepoExists && publicRepo.(bool) &&
  274. ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
  275. ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
  276. return
  277. }
  278. if pubOrgExists && publicOrg.(bool) &&
  279. ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
  280. ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
  281. return
  282. }
  283. return
  284. }
  285. if ctx.IsBasicAuth {
  286. ctx.CheckForOTP()
  287. return
  288. }
  289. if ctx.IsSigned {
  290. return
  291. }
  292. ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
  293. }
  294. }
  295. func reqExploreSignIn() func(ctx *context.APIContext) {
  296. return func(ctx *context.APIContext) {
  297. if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
  298. ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
  299. }
  300. }
  301. }
  302. func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
  303. return func(ctx *context.APIContext) {
  304. if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
  305. return
  306. }
  307. if !ctx.IsBasicAuth {
  308. ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
  309. return
  310. }
  311. ctx.CheckForOTP()
  312. }
  313. }
  314. // reqSiteAdmin user should be the site admin
  315. func reqSiteAdmin() func(ctx *context.APIContext) {
  316. return func(ctx *context.APIContext) {
  317. if !ctx.IsUserSiteAdmin() {
  318. ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
  319. return
  320. }
  321. }
  322. }
  323. // reqOwner user should be the owner of the repo or site admin.
  324. func reqOwner() func(ctx *context.APIContext) {
  325. return func(ctx *context.APIContext) {
  326. if !ctx.Repo.IsOwner() && !ctx.IsUserSiteAdmin() {
  327. ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
  328. return
  329. }
  330. }
  331. }
  332. // reqSelfOrAdmin doer should be the same as the contextUser or site admin
  333. func reqSelfOrAdmin() func(ctx *context.APIContext) {
  334. return func(ctx *context.APIContext) {
  335. if !ctx.IsUserSiteAdmin() && ctx.ContextUser != ctx.Doer {
  336. ctx.Error(http.StatusForbidden, "reqSelfOrAdmin", "doer should be the site admin or be same as the contextUser")
  337. return
  338. }
  339. }
  340. }
  341. // reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin
  342. func reqAdmin() func(ctx *context.APIContext) {
  343. return func(ctx *context.APIContext) {
  344. if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  345. ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
  346. return
  347. }
  348. }
  349. }
  350. // reqRepoWriter user should have a permission to write to a repo, or be a site admin
  351. func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
  352. return func(ctx *context.APIContext) {
  353. if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  354. ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
  355. return
  356. }
  357. }
  358. }
  359. // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
  360. func reqRepoBranchWriter(ctx *context.APIContext) {
  361. options, ok := web.GetForm(ctx).(api.FileOptionInterface)
  362. if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
  363. ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch")
  364. return
  365. }
  366. }
  367. // reqRepoReader user should have specific read permission or be a repo admin or a site admin
  368. func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
  369. return func(ctx *context.APIContext) {
  370. if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
  371. ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
  372. return
  373. }
  374. }
  375. }
  376. // reqAnyRepoReader user should have any permission to read repository or permissions of site admin
  377. func reqAnyRepoReader() func(ctx *context.APIContext) {
  378. return func(ctx *context.APIContext) {
  379. if !ctx.Repo.HasAccess() && !ctx.IsUserSiteAdmin() {
  380. ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
  381. return
  382. }
  383. }
  384. }
  385. // reqOrgOwnership user should be an organization owner, or a site admin
  386. func reqOrgOwnership() func(ctx *context.APIContext) {
  387. return func(ctx *context.APIContext) {
  388. if ctx.IsUserSiteAdmin() {
  389. return
  390. }
  391. var orgID int64
  392. if ctx.Org.Organization != nil {
  393. orgID = ctx.Org.Organization.ID
  394. } else if ctx.Org.Team != nil {
  395. orgID = ctx.Org.Team.OrgID
  396. } else {
  397. ctx.Error(http.StatusInternalServerError, "", "reqOrgOwnership: unprepared context")
  398. return
  399. }
  400. isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
  401. if err != nil {
  402. ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
  403. return
  404. } else if !isOwner {
  405. if ctx.Org.Organization != nil {
  406. ctx.Error(http.StatusForbidden, "", "Must be an organization owner")
  407. } else {
  408. ctx.NotFound()
  409. }
  410. return
  411. }
  412. }
  413. }
  414. // reqTeamMembership user should be an team member, or a site admin
  415. func reqTeamMembership() func(ctx *context.APIContext) {
  416. return func(ctx *context.APIContext) {
  417. if ctx.IsUserSiteAdmin() {
  418. return
  419. }
  420. if ctx.Org.Team == nil {
  421. ctx.Error(http.StatusInternalServerError, "", "reqTeamMembership: unprepared context")
  422. return
  423. }
  424. orgID := ctx.Org.Team.OrgID
  425. isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID)
  426. if err != nil {
  427. ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err)
  428. return
  429. } else if isOwner {
  430. return
  431. }
  432. if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil {
  433. ctx.Error(http.StatusInternalServerError, "IsTeamMember", err)
  434. return
  435. } else if !isTeamMember {
  436. isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID)
  437. if err != nil {
  438. ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
  439. } else if isOrgMember {
  440. ctx.Error(http.StatusForbidden, "", "Must be a team member")
  441. } else {
  442. ctx.NotFound()
  443. }
  444. return
  445. }
  446. }
  447. }
  448. // reqOrgMembership user should be an organization member, or a site admin
  449. func reqOrgMembership() func(ctx *context.APIContext) {
  450. return func(ctx *context.APIContext) {
  451. if ctx.IsUserSiteAdmin() {
  452. return
  453. }
  454. var orgID int64
  455. if ctx.Org.Organization != nil {
  456. orgID = ctx.Org.Organization.ID
  457. } else if ctx.Org.Team != nil {
  458. orgID = ctx.Org.Team.OrgID
  459. } else {
  460. ctx.Error(http.StatusInternalServerError, "", "reqOrgMembership: unprepared context")
  461. return
  462. }
  463. if isMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID); err != nil {
  464. ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
  465. return
  466. } else if !isMember {
  467. if ctx.Org.Organization != nil {
  468. ctx.Error(http.StatusForbidden, "", "Must be an organization member")
  469. } else {
  470. ctx.NotFound()
  471. }
  472. return
  473. }
  474. }
  475. }
  476. func reqGitHook() func(ctx *context.APIContext) {
  477. return func(ctx *context.APIContext) {
  478. if !ctx.Doer.CanEditGitHook() {
  479. ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
  480. return
  481. }
  482. }
  483. }
  484. // reqWebhooksEnabled requires webhooks to be enabled by admin.
  485. func reqWebhooksEnabled() func(ctx *context.APIContext) {
  486. return func(ctx *context.APIContext) {
  487. if setting.DisableWebhooks {
  488. ctx.Error(http.StatusForbidden, "", "webhooks disabled by administrator")
  489. return
  490. }
  491. }
  492. }
  493. func orgAssignment(args ...bool) func(ctx *context.APIContext) {
  494. var (
  495. assignOrg bool
  496. assignTeam bool
  497. )
  498. if len(args) > 0 {
  499. assignOrg = args[0]
  500. }
  501. if len(args) > 1 {
  502. assignTeam = args[1]
  503. }
  504. return func(ctx *context.APIContext) {
  505. ctx.Org = new(context.APIOrganization)
  506. var err error
  507. if assignOrg {
  508. ctx.Org.Organization, err = organization.GetOrgByName(ctx, ctx.Params(":org"))
  509. if err != nil {
  510. if organization.IsErrOrgNotExist(err) {
  511. redirectUserID, err := user_model.LookupUserRedirect(ctx, ctx.Params(":org"))
  512. if err == nil {
  513. context.RedirectToUser(ctx.Base, ctx.Params(":org"), redirectUserID)
  514. } else if user_model.IsErrUserRedirectNotExist(err) {
  515. ctx.NotFound("GetOrgByName", err)
  516. } else {
  517. ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
  518. }
  519. } else {
  520. ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
  521. }
  522. return
  523. }
  524. ctx.ContextUser = ctx.Org.Organization.AsUser()
  525. }
  526. if assignTeam {
  527. ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.ParamsInt64(":teamid"))
  528. if err != nil {
  529. if organization.IsErrTeamNotExist(err) {
  530. ctx.NotFound()
  531. } else {
  532. ctx.Error(http.StatusInternalServerError, "GetTeamById", err)
  533. }
  534. return
  535. }
  536. }
  537. }
  538. }
  539. func mustEnableIssues(ctx *context.APIContext) {
  540. if !ctx.Repo.CanRead(unit.TypeIssues) {
  541. if log.IsTrace() {
  542. if ctx.IsSigned {
  543. log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
  544. "User in Repo has Permissions: %-+v",
  545. ctx.Doer,
  546. unit.TypeIssues,
  547. ctx.Repo.Repository,
  548. ctx.Repo.Permission)
  549. } else {
  550. log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
  551. "Anonymous user in Repo has Permissions: %-+v",
  552. unit.TypeIssues,
  553. ctx.Repo.Repository,
  554. ctx.Repo.Permission)
  555. }
  556. }
  557. ctx.NotFound()
  558. return
  559. }
  560. }
  561. func mustAllowPulls(ctx *context.APIContext) {
  562. if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
  563. if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
  564. if ctx.IsSigned {
  565. log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
  566. "User in Repo has Permissions: %-+v",
  567. ctx.Doer,
  568. unit.TypePullRequests,
  569. ctx.Repo.Repository,
  570. ctx.Repo.Permission)
  571. } else {
  572. log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
  573. "Anonymous user in Repo has Permissions: %-+v",
  574. unit.TypePullRequests,
  575. ctx.Repo.Repository,
  576. ctx.Repo.Permission)
  577. }
  578. }
  579. ctx.NotFound()
  580. return
  581. }
  582. }
  583. func mustEnableIssuesOrPulls(ctx *context.APIContext) {
  584. if !ctx.Repo.CanRead(unit.TypeIssues) &&
  585. !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
  586. if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
  587. if ctx.IsSigned {
  588. log.Trace("Permission Denied: User %-v cannot read %-v and %-v in Repo %-v\n"+
  589. "User in Repo has Permissions: %-+v",
  590. ctx.Doer,
  591. unit.TypeIssues,
  592. unit.TypePullRequests,
  593. ctx.Repo.Repository,
  594. ctx.Repo.Permission)
  595. } else {
  596. log.Trace("Permission Denied: Anonymous user cannot read %-v and %-v in Repo %-v\n"+
  597. "Anonymous user in Repo has Permissions: %-+v",
  598. unit.TypeIssues,
  599. unit.TypePullRequests,
  600. ctx.Repo.Repository,
  601. ctx.Repo.Permission)
  602. }
  603. }
  604. ctx.NotFound()
  605. return
  606. }
  607. }
  608. func mustEnableWiki(ctx *context.APIContext) {
  609. if !(ctx.Repo.CanRead(unit.TypeWiki)) {
  610. ctx.NotFound()
  611. return
  612. }
  613. }
  614. func mustNotBeArchived(ctx *context.APIContext) {
  615. if ctx.Repo.Repository.IsArchived {
  616. ctx.NotFound()
  617. return
  618. }
  619. }
  620. func mustEnableAttachments(ctx *context.APIContext) {
  621. if !setting.Attachment.Enabled {
  622. ctx.NotFound()
  623. return
  624. }
  625. }
  626. // bind binding an obj to a func(ctx *context.APIContext)
  627. func bind[T any](_ T) any {
  628. return func(ctx *context.APIContext) {
  629. theObj := new(T) // create a new form obj for every request but not use obj directly
  630. errs := binding.Bind(ctx.Req, theObj)
  631. if len(errs) > 0 {
  632. ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
  633. return
  634. }
  635. web.SetForm(ctx, theObj)
  636. }
  637. }
  638. // The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
  639. // in the session (if there is a user id stored in session other plugins might return the user
  640. // object for that id).
  641. //
  642. // The Session plugin is expected to be executed second, in order to skip authentication
  643. // for users that have already signed in.
  644. func buildAuthGroup() *auth.Group {
  645. group := auth.NewGroup(
  646. &auth.OAuth2{},
  647. &auth.HTTPSign{},
  648. &auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
  649. )
  650. if setting.Service.EnableReverseProxyAuthAPI {
  651. group.Add(&auth.ReverseProxy{})
  652. }
  653. if setting.IsWindows && auth_model.IsSSPIEnabled() {
  654. group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
  655. }
  656. return group
  657. }
  658. func apiAuth(authMethod auth.Method) func(*context.APIContext) {
  659. return func(ctx *context.APIContext) {
  660. ar, err := common.AuthShared(ctx.Base, nil, authMethod)
  661. if err != nil {
  662. ctx.Error(http.StatusUnauthorized, "APIAuth", err)
  663. return
  664. }
  665. ctx.Doer = ar.Doer
  666. ctx.IsSigned = ar.Doer != nil
  667. ctx.IsBasicAuth = ar.IsBasicAuth
  668. }
  669. }
  670. // verifyAuthWithOptions checks authentication according to options
  671. func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) {
  672. return func(ctx *context.APIContext) {
  673. // Check prohibit login users.
  674. if ctx.IsSigned {
  675. if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
  676. ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
  677. ctx.JSON(http.StatusForbidden, map[string]string{
  678. "message": "This account is not activated.",
  679. })
  680. return
  681. }
  682. if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
  683. log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
  684. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  685. ctx.JSON(http.StatusForbidden, map[string]string{
  686. "message": "This account is prohibited from signing in, please contact your site administrator.",
  687. })
  688. return
  689. }
  690. if ctx.Doer.MustChangePassword {
  691. ctx.JSON(http.StatusForbidden, map[string]string{
  692. "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
  693. })
  694. return
  695. }
  696. }
  697. // Redirect to dashboard if user tries to visit any non-login page.
  698. if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
  699. ctx.Redirect(setting.AppSubURL + "/")
  700. return
  701. }
  702. if options.SignInRequired {
  703. if !ctx.IsSigned {
  704. // Restrict API calls with error message.
  705. ctx.JSON(http.StatusForbidden, map[string]string{
  706. "message": "Only signed in user is allowed to call APIs.",
  707. })
  708. return
  709. } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
  710. ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
  711. ctx.JSON(http.StatusForbidden, map[string]string{
  712. "message": "This account is not activated.",
  713. })
  714. return
  715. }
  716. if ctx.IsSigned && ctx.IsBasicAuth {
  717. if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
  718. return // Skip 2FA
  719. }
  720. twofa, err := auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
  721. if err != nil {
  722. if auth_model.IsErrTwoFactorNotEnrolled(err) {
  723. return // No 2FA enrollment for this user
  724. }
  725. ctx.InternalServerError(err)
  726. return
  727. }
  728. otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
  729. ok, err := twofa.ValidateTOTP(otpHeader)
  730. if err != nil {
  731. ctx.InternalServerError(err)
  732. return
  733. }
  734. if !ok {
  735. ctx.JSON(http.StatusForbidden, map[string]string{
  736. "message": "Only signed in user is allowed to call APIs.",
  737. })
  738. return
  739. }
  740. }
  741. }
  742. if options.AdminRequired {
  743. if !ctx.Doer.IsAdmin {
  744. ctx.JSON(http.StatusForbidden, map[string]string{
  745. "message": "You have no permission to request for this.",
  746. })
  747. return
  748. }
  749. }
  750. }
  751. }
  752. // Routes registers all v1 APIs routes to web application.
  753. func Routes() *web.Route {
  754. m := web.NewRoute()
  755. m.Use(securityHeaders())
  756. if setting.CORSConfig.Enabled {
  757. m.Use(cors.Handler(cors.Options{
  758. // Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
  759. AllowedOrigins: setting.CORSConfig.AllowDomain,
  760. // setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
  761. AllowedMethods: setting.CORSConfig.Methods,
  762. AllowCredentials: setting.CORSConfig.AllowCredentials,
  763. AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
  764. MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
  765. }))
  766. }
  767. m.Use(context.APIContexter())
  768. // Get user from session if logged in.
  769. m.Use(apiAuth(buildAuthGroup()))
  770. m.Use(verifyAuthWithOptions(&common.VerifyOptions{
  771. SignInRequired: setting.Service.RequireSignInView,
  772. }))
  773. m.Group("", func() {
  774. // Miscellaneous (no scope required)
  775. if setting.API.EnableSwagger {
  776. m.Get("/swagger", func(ctx *context.APIContext) {
  777. ctx.Redirect(setting.AppSubURL + "/api/swagger")
  778. })
  779. }
  780. if setting.Federation.Enabled {
  781. m.Get("/nodeinfo", misc.NodeInfo)
  782. m.Group("/activitypub", func() {
  783. // deprecated, remove in 1.20, use /user-id/{user-id} instead
  784. m.Group("/user/{username}", func() {
  785. m.Get("", activitypub.Person)
  786. m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
  787. }, context_service.UserAssignmentAPI())
  788. m.Group("/user-id/{user-id}", func() {
  789. m.Get("", activitypub.Person)
  790. m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
  791. }, context_service.UserIDAssignmentAPI())
  792. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
  793. }
  794. // Misc (public accessible)
  795. m.Group("", func() {
  796. m.Get("/version", misc.Version)
  797. m.Get("/signing-key.gpg", misc.SigningKey)
  798. m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
  799. m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
  800. m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
  801. m.Get("/gitignore/templates", misc.ListGitignoresTemplates)
  802. m.Get("/gitignore/templates/{name}", misc.GetGitignoreTemplateInfo)
  803. m.Get("/licenses", misc.ListLicenseTemplates)
  804. m.Get("/licenses/{name}", misc.GetLicenseTemplateInfo)
  805. m.Get("/label/templates", misc.ListLabelTemplates)
  806. m.Get("/label/templates/{name}", misc.GetLabelTemplate)
  807. m.Group("/settings", func() {
  808. m.Get("/ui", settings.GetGeneralUISettings)
  809. m.Get("/api", settings.GetGeneralAPISettings)
  810. m.Get("/attachment", settings.GetGeneralAttachmentSettings)
  811. m.Get("/repository", settings.GetGeneralRepoSettings)
  812. })
  813. })
  814. // Notifications (requires 'notifications' scope)
  815. m.Group("/notifications", func() {
  816. m.Combo("").
  817. Get(reqToken(), notify.ListNotifications).
  818. Put(reqToken(), notify.ReadNotifications)
  819. m.Get("/new", reqToken(), notify.NewAvailable)
  820. m.Combo("/threads/{id}").
  821. Get(reqToken(), notify.GetThread).
  822. Patch(reqToken(), notify.ReadThread)
  823. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
  824. // Users (requires user scope)
  825. m.Group("/users", func() {
  826. m.Get("/search", reqExploreSignIn(), user.Search)
  827. m.Group("/{username}", func() {
  828. m.Get("", reqExploreSignIn(), user.GetInfo)
  829. if setting.Service.EnableUserHeatmap {
  830. m.Get("/heatmap", user.GetUserHeatmapData)
  831. }
  832. m.Get("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqExploreSignIn(), user.ListUserRepos)
  833. m.Group("/tokens", func() {
  834. m.Combo("").Get(user.ListAccessTokens).
  835. Post(bind(api.CreateAccessTokenOption{}), reqToken(), user.CreateAccessToken)
  836. m.Combo("/{id}").Delete(reqToken(), user.DeleteAccessToken)
  837. }, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
  838. m.Get("/activities/feeds", user.ListUserActivityFeeds)
  839. }, context_service.UserAssignmentAPI())
  840. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
  841. // Users (requires user scope)
  842. m.Group("/users", func() {
  843. m.Group("/{username}", func() {
  844. m.Get("/keys", user.ListPublicKeys)
  845. m.Get("/gpg_keys", user.ListGPGKeys)
  846. m.Get("/followers", user.ListFollowers)
  847. m.Group("/following", func() {
  848. m.Get("", user.ListFollowing)
  849. m.Get("/{target}", user.CheckFollowing)
  850. })
  851. m.Get("/starred", user.GetStarredRepos)
  852. m.Get("/subscriptions", user.GetWatchedRepos)
  853. }, context_service.UserAssignmentAPI())
  854. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
  855. // Users (requires user scope)
  856. m.Group("/user", func() {
  857. m.Get("", user.GetAuthenticatedUser)
  858. m.Group("/settings", func() {
  859. m.Get("", user.GetUserSettings)
  860. m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings)
  861. }, reqToken())
  862. m.Combo("/emails").
  863. Get(user.ListEmails).
  864. Post(bind(api.CreateEmailOption{}), user.AddEmail).
  865. Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
  866. // create or update a user's actions secrets
  867. m.Group("/actions/secrets", func() {
  868. m.Combo("/{secretname}").
  869. Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret).
  870. Delete(repo.DeleteSecret)
  871. })
  872. m.Get("/followers", user.ListMyFollowers)
  873. m.Group("/following", func() {
  874. m.Get("", user.ListMyFollowing)
  875. m.Group("/{username}", func() {
  876. m.Get("", user.CheckMyFollowing)
  877. m.Put("", user.Follow)
  878. m.Delete("", user.Unfollow)
  879. }, context_service.UserAssignmentAPI())
  880. })
  881. // (admin:public_key scope)
  882. m.Group("/keys", func() {
  883. m.Combo("").Get(user.ListMyPublicKeys).
  884. Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
  885. m.Combo("/{id}").Get(user.GetPublicKey).
  886. Delete(user.DeletePublicKey)
  887. })
  888. // (admin:application scope)
  889. m.Group("/applications", func() {
  890. m.Combo("/oauth2").
  891. Get(user.ListOauth2Applications).
  892. Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application)
  893. m.Combo("/oauth2/{id}").
  894. Delete(user.DeleteOauth2Application).
  895. Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
  896. Get(user.GetOauth2Application)
  897. })
  898. // (admin:gpg_key scope)
  899. m.Group("/gpg_keys", func() {
  900. m.Combo("").Get(user.ListMyGPGKeys).
  901. Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
  902. m.Combo("/{id}").Get(user.GetGPGKey).
  903. Delete(user.DeleteGPGKey)
  904. })
  905. m.Get("/gpg_key_token", user.GetVerificationToken)
  906. m.Post("/gpg_key_verify", bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
  907. // (repo scope)
  908. m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
  909. Post(bind(api.CreateRepoOption{}), repo.Create)
  910. // (repo scope)
  911. m.Group("/starred", func() {
  912. m.Get("", user.GetMyStarredRepos)
  913. m.Group("/{username}/{reponame}", func() {
  914. m.Get("", user.IsStarring)
  915. m.Put("", user.Star)
  916. m.Delete("", user.Unstar)
  917. }, repoAssignment())
  918. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
  919. m.Get("/times", repo.ListMyTrackedTimes)
  920. m.Get("/stopwatches", repo.GetStopwatches)
  921. m.Get("/subscriptions", user.GetMyWatchedRepos)
  922. m.Get("/teams", org.ListUserTeams)
  923. m.Group("/hooks", func() {
  924. m.Combo("").Get(user.ListHooks).
  925. Post(bind(api.CreateHookOption{}), user.CreateHook)
  926. m.Combo("/{id}").Get(user.GetHook).
  927. Patch(bind(api.EditHookOption{}), user.EditHook).
  928. Delete(user.DeleteHook)
  929. }, reqWebhooksEnabled())
  930. m.Group("/avatar", func() {
  931. m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
  932. m.Delete("", user.DeleteAvatar)
  933. }, reqToken())
  934. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
  935. // Repositories (requires repo scope, org scope)
  936. m.Post("/org/{org}/repos",
  937. tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
  938. reqToken(),
  939. bind(api.CreateRepoOption{}),
  940. repo.CreateOrgRepoDeprecated)
  941. // requires repo scope
  942. m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
  943. // Repos (requires repo scope)
  944. m.Group("/repos", func() {
  945. m.Get("/search", repo.Search)
  946. // (repo scope)
  947. m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
  948. m.Group("/{username}/{reponame}", func() {
  949. m.Combo("").Get(reqAnyRepoReader(), repo.Get).
  950. Delete(reqToken(), reqOwner(), repo.Delete).
  951. Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
  952. m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
  953. m.Group("/transfer", func() {
  954. m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
  955. m.Post("/accept", repo.AcceptTransfer)
  956. m.Post("/reject", repo.RejectTransfer)
  957. }, reqToken())
  958. m.Group("/actions/secrets", func() {
  959. m.Combo("/{secretname}").
  960. Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
  961. Delete(reqToken(), reqOwner(), repo.DeleteSecret)
  962. })
  963. m.Group("/hooks/git", func() {
  964. m.Combo("").Get(repo.ListGitHooks)
  965. m.Group("/{id}", func() {
  966. m.Combo("").Get(repo.GetGitHook).
  967. Patch(bind(api.EditGitHookOption{}), repo.EditGitHook).
  968. Delete(repo.DeleteGitHook)
  969. })
  970. }, reqToken(), reqAdmin(), reqGitHook(), context.ReferencesGitRepo(true))
  971. m.Group("/hooks", func() {
  972. m.Combo("").Get(repo.ListHooks).
  973. Post(bind(api.CreateHookOption{}), repo.CreateHook)
  974. m.Group("/{id}", func() {
  975. m.Combo("").Get(repo.GetHook).
  976. Patch(bind(api.EditHookOption{}), repo.EditHook).
  977. Delete(repo.DeleteHook)
  978. m.Post("/tests", context.ReferencesGitRepo(), context.RepoRefForAPI, repo.TestHook)
  979. })
  980. }, reqToken(), reqAdmin(), reqWebhooksEnabled())
  981. m.Group("/collaborators", func() {
  982. m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
  983. m.Group("/{collaborator}", func() {
  984. m.Combo("").Get(reqAnyRepoReader(), repo.IsCollaborator).
  985. Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
  986. Delete(reqAdmin(), repo.DeleteCollaborator)
  987. m.Get("/permission", repo.GetRepoPermissions)
  988. })
  989. }, reqToken())
  990. m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
  991. m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
  992. m.Group("/teams", func() {
  993. m.Get("", reqAnyRepoReader(), repo.ListTeams)
  994. m.Combo("/{team}").Get(reqAnyRepoReader(), repo.IsTeam).
  995. Put(reqAdmin(), repo.AddTeam).
  996. Delete(reqAdmin(), repo.DeleteTeam)
  997. }, reqToken())
  998. m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
  999. m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
  1000. m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
  1001. m.Combo("/forks").Get(repo.ListForks).
  1002. Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
  1003. m.Group("/branches", func() {
  1004. m.Get("", repo.ListBranches)
  1005. m.Get("/*", repo.GetBranch)
  1006. m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), repo.DeleteBranch)
  1007. m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
  1008. }, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
  1009. m.Group("/branch_protections", func() {
  1010. m.Get("", repo.ListBranchProtections)
  1011. m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection)
  1012. m.Group("/{name}", func() {
  1013. m.Get("", repo.GetBranchProtection)
  1014. m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection)
  1015. m.Delete("", repo.DeleteBranchProtection)
  1016. })
  1017. }, reqToken(), reqAdmin())
  1018. m.Group("/tags", func() {
  1019. m.Get("", repo.ListTags)
  1020. m.Get("/*", repo.GetTag)
  1021. m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateTagOption{}), repo.CreateTag)
  1022. m.Delete("/*", reqToken(), repo.DeleteTag)
  1023. }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
  1024. m.Group("/keys", func() {
  1025. m.Combo("").Get(repo.ListDeployKeys).
  1026. Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
  1027. m.Combo("/{id}").Get(repo.GetDeployKey).
  1028. Delete(repo.DeleteDeploykey)
  1029. }, reqToken(), reqAdmin())
  1030. m.Group("/times", func() {
  1031. m.Combo("").Get(repo.ListTrackedTimesByRepository)
  1032. m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
  1033. }, mustEnableIssues, reqToken())
  1034. m.Group("/wiki", func() {
  1035. m.Combo("/page/{pageName}").
  1036. Get(repo.GetWikiPage).
  1037. Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
  1038. Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
  1039. m.Get("/revisions/{pageName}", repo.ListPageRevisions)
  1040. m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
  1041. m.Get("/pages", repo.ListWikiPages)
  1042. }, mustEnableWiki)
  1043. m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
  1044. m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
  1045. m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
  1046. m.Get("/stargazers", repo.ListStargazers)
  1047. m.Get("/subscribers", repo.ListSubscribers)
  1048. m.Group("/subscription", func() {
  1049. m.Get("", user.IsWatching)
  1050. m.Put("", reqToken(), user.Watch)
  1051. m.Delete("", reqToken(), user.Unwatch)
  1052. })
  1053. m.Group("/releases", func() {
  1054. m.Combo("").Get(repo.ListReleases).
  1055. Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
  1056. m.Combo("/latest").Get(repo.GetLatestRelease)
  1057. m.Group("/{id}", func() {
  1058. m.Combo("").Get(repo.GetRelease).
  1059. Patch(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease).
  1060. Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteRelease)
  1061. m.Group("/assets", func() {
  1062. m.Combo("").Get(repo.ListReleaseAttachments).
  1063. Post(reqToken(), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment)
  1064. m.Combo("/{attachment_id}").Get(repo.GetReleaseAttachment).
  1065. Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
  1066. Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment)
  1067. })
  1068. })
  1069. m.Group("/tags", func() {
  1070. m.Combo("/{tag}").
  1071. Get(repo.GetReleaseByTag).
  1072. Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
  1073. })
  1074. }, reqRepoReader(unit.TypeReleases))
  1075. m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), repo.MirrorSync)
  1076. m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), repo.PushMirrorSync)
  1077. m.Group("/push_mirrors", func() {
  1078. m.Combo("").Get(repo.ListPushMirrors).
  1079. Post(bind(api.CreatePushMirrorOption{}), repo.AddPushMirror)
  1080. m.Combo("/{name}").
  1081. Delete(repo.DeletePushMirrorByRemoteName).
  1082. Get(repo.GetPushMirrorByName)
  1083. }, reqAdmin(), reqToken())
  1084. m.Get("/editorconfig/{filename}", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetEditorconfig)
  1085. m.Group("/pulls", func() {
  1086. m.Combo("").Get(repo.ListPullRequests).
  1087. Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
  1088. m.Get("/pinned", repo.ListPinnedPullRequests)
  1089. m.Group("/{index}", func() {
  1090. m.Combo("").Get(repo.GetPullRequest).
  1091. Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
  1092. m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch)
  1093. m.Post("/update", reqToken(), repo.UpdatePullRequest)
  1094. m.Get("/commits", repo.GetPullRequestCommits)
  1095. m.Get("/files", repo.GetPullRequestFiles)
  1096. m.Combo("/merge").Get(repo.IsPullRequestMerged).
  1097. Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest).
  1098. Delete(reqToken(), mustNotBeArchived, repo.CancelScheduledAutoMerge)
  1099. m.Group("/reviews", func() {
  1100. m.Combo("").
  1101. Get(repo.ListPullReviews).
  1102. Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)
  1103. m.Group("/{id}", func() {
  1104. m.Combo("").
  1105. Get(repo.GetPullReview).
  1106. Delete(reqToken(), repo.DeletePullReview).
  1107. Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)
  1108. m.Combo("/comments").
  1109. Get(repo.GetPullReviewComments)
  1110. m.Post("/dismissals", reqToken(), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview)
  1111. m.Post("/undismissals", reqToken(), repo.UnDismissPullReview)
  1112. })
  1113. })
  1114. m.Combo("/requested_reviewers", reqToken()).
  1115. Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
  1116. Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
  1117. })
  1118. }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
  1119. m.Group("/statuses", func() {
  1120. m.Combo("/{sha}").Get(repo.GetCommitStatuses).
  1121. Post(reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
  1122. }, reqRepoReader(unit.TypeCode))
  1123. m.Group("/commits", func() {
  1124. m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits)
  1125. m.Group("/{ref}", func() {
  1126. m.Get("/status", repo.GetCombinedCommitStatusByRef)
  1127. m.Get("/statuses", repo.GetCommitStatusesByRef)
  1128. }, context.ReferencesGitRepo())
  1129. }, reqRepoReader(unit.TypeCode))
  1130. m.Group("/git", func() {
  1131. m.Group("/commits", func() {
  1132. m.Get("/{sha}", repo.GetSingleCommit)
  1133. m.Get("/{sha}.{diffType:diff|patch}", repo.DownloadCommitDiffOrPatch)
  1134. })
  1135. m.Get("/refs", repo.GetGitAllRefs)
  1136. m.Get("/refs/*", repo.GetGitRefs)
  1137. m.Get("/trees/{sha}", repo.GetTree)
  1138. m.Get("/blobs/{sha}", repo.GetBlob)
  1139. m.Get("/tags/{sha}", repo.GetAnnotatedTag)
  1140. m.Get("/notes/{sha}", repo.GetNote)
  1141. }, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
  1142. m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch)
  1143. m.Group("/contents", func() {
  1144. m.Get("", repo.GetContentsList)
  1145. m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, repo.ChangeFiles)
  1146. m.Get("/*", repo.GetContents)
  1147. m.Group("/*", func() {
  1148. m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, repo.CreateFile)
  1149. m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, repo.UpdateFile)
  1150. m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, repo.DeleteFile)
  1151. }, reqToken())
  1152. }, reqRepoReader(unit.TypeCode))
  1153. m.Get("/signing-key.gpg", misc.SigningKey)
  1154. m.Group("/topics", func() {
  1155. m.Combo("").Get(repo.ListTopics).
  1156. Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
  1157. m.Group("/{topic}", func() {
  1158. m.Combo("").Put(reqToken(), repo.AddTopic).
  1159. Delete(reqToken(), repo.DeleteTopic)
  1160. }, reqAdmin())
  1161. }, reqAnyRepoReader())
  1162. m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates)
  1163. m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
  1164. m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig)
  1165. m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
  1166. m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
  1167. m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
  1168. m.Group("/avatar", func() {
  1169. m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
  1170. m.Delete("", repo.DeleteAvatar)
  1171. }, reqAdmin(), reqToken())
  1172. }, repoAssignment())
  1173. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
  1174. // Notifications (requires notifications scope)
  1175. m.Group("/repos", func() {
  1176. m.Group("/{username}/{reponame}", func() {
  1177. m.Combo("/notifications", reqToken()).
  1178. Get(notify.ListRepoNotifications).
  1179. Put(notify.ReadRepoNotifications)
  1180. }, repoAssignment())
  1181. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
  1182. // Issue (requires issue scope)
  1183. m.Group("/repos", func() {
  1184. m.Get("/issues/search", repo.SearchIssues)
  1185. m.Group("/{username}/{reponame}", func() {
  1186. m.Group("/issues", func() {
  1187. m.Combo("").Get(repo.ListIssues).
  1188. Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
  1189. m.Get("/pinned", repo.ListPinnedIssues)
  1190. m.Group("/comments", func() {
  1191. m.Get("", repo.ListRepoIssueComments)
  1192. m.Group("/{id}", func() {
  1193. m.Combo("").
  1194. Get(repo.GetIssueComment).
  1195. Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
  1196. Delete(reqToken(), repo.DeleteIssueComment)
  1197. m.Combo("/reactions").
  1198. Get(repo.GetIssueCommentReactions).
  1199. Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
  1200. Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
  1201. m.Group("/assets", func() {
  1202. m.Combo("").
  1203. Get(repo.ListIssueCommentAttachments).
  1204. Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment)
  1205. m.Combo("/{attachment_id}").
  1206. Get(repo.GetIssueCommentAttachment).
  1207. Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
  1208. Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment)
  1209. }, mustEnableAttachments)
  1210. })
  1211. })
  1212. m.Group("/{index}", func() {
  1213. m.Combo("").Get(repo.GetIssue).
  1214. Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
  1215. Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
  1216. m.Group("/comments", func() {
  1217. m.Combo("").Get(repo.ListIssueComments).
  1218. Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
  1219. m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
  1220. Delete(repo.DeleteIssueCommentDeprecated)
  1221. })
  1222. m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
  1223. m.Group("/labels", func() {
  1224. m.Combo("").Get(repo.ListIssueLabels).
  1225. Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
  1226. Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
  1227. Delete(reqToken(), repo.ClearIssueLabels)
  1228. m.Delete("/{id}", reqToken(), repo.DeleteIssueLabel)
  1229. })
  1230. m.Group("/times", func() {
  1231. m.Combo("").
  1232. Get(repo.ListTrackedTimes).
  1233. Post(bind(api.AddTimeOption{}), repo.AddTime).
  1234. Delete(repo.ResetIssueTime)
  1235. m.Delete("/{id}", repo.DeleteTime)
  1236. }, reqToken())
  1237. m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
  1238. m.Group("/stopwatch", func() {
  1239. m.Post("/start", repo.StartIssueStopwatch)
  1240. m.Post("/stop", repo.StopIssueStopwatch)
  1241. m.Delete("/delete", repo.DeleteIssueStopwatch)
  1242. }, reqToken())
  1243. m.Group("/subscriptions", func() {
  1244. m.Get("", repo.GetIssueSubscribers)
  1245. m.Get("/check", reqToken(), repo.CheckIssueSubscription)
  1246. m.Put("/{user}", reqToken(), repo.AddIssueSubscription)
  1247. m.Delete("/{user}", reqToken(), repo.DelIssueSubscription)
  1248. })
  1249. m.Combo("/reactions").
  1250. Get(repo.GetIssueReactions).
  1251. Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueReaction).
  1252. Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
  1253. m.Group("/assets", func() {
  1254. m.Combo("").
  1255. Get(repo.ListIssueAttachments).
  1256. Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment)
  1257. m.Combo("/{attachment_id}").
  1258. Get(repo.GetIssueAttachment).
  1259. Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment).
  1260. Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueAttachment)
  1261. }, mustEnableAttachments)
  1262. m.Combo("/dependencies").
  1263. Get(repo.GetIssueDependencies).
  1264. Post(reqToken(), mustNotBeArchived, bind(api.IssueMeta{}), repo.CreateIssueDependency).
  1265. Delete(reqToken(), mustNotBeArchived, bind(api.IssueMeta{}), repo.RemoveIssueDependency)
  1266. m.Combo("/blocks").
  1267. Get(repo.GetIssueBlocks).
  1268. Post(reqToken(), bind(api.IssueMeta{}), repo.CreateIssueBlocking).
  1269. Delete(reqToken(), bind(api.IssueMeta{}), repo.RemoveIssueBlocking)
  1270. m.Group("/pin", func() {
  1271. m.Combo("").
  1272. Post(reqToken(), reqAdmin(), repo.PinIssue).
  1273. Delete(reqToken(), reqAdmin(), repo.UnpinIssue)
  1274. m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin)
  1275. })
  1276. })
  1277. }, mustEnableIssuesOrPulls)
  1278. m.Group("/labels", func() {
  1279. m.Combo("").Get(repo.ListLabels).
  1280. Post(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel)
  1281. m.Combo("/{id}").Get(repo.GetLabel).
  1282. Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel).
  1283. Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteLabel)
  1284. })
  1285. m.Group("/milestones", func() {
  1286. m.Combo("").Get(repo.ListMilestones).
  1287. Post(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
  1288. m.Combo("/{id}").Get(repo.GetMilestone).
  1289. Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
  1290. Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
  1291. })
  1292. }, repoAssignment())
  1293. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
  1294. // NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
  1295. m.Group("/packages/{username}", func() {
  1296. m.Group("/{type}/{name}/{version}", func() {
  1297. m.Get("", reqToken(), packages.GetPackage)
  1298. m.Delete("", reqToken(), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
  1299. m.Get("/files", reqToken(), packages.ListPackageFiles)
  1300. })
  1301. m.Get("/", reqToken(), packages.ListPackages)
  1302. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
  1303. // Organizations
  1304. m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
  1305. m.Group("/users/{username}/orgs", func() {
  1306. m.Get("", reqToken(), org.ListUserOrgs)
  1307. m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
  1308. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI())
  1309. m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
  1310. m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
  1311. m.Group("/orgs/{org}", func() {
  1312. m.Combo("").Get(org.Get).
  1313. Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
  1314. Delete(reqToken(), reqOrgOwnership(), org.Delete)
  1315. m.Combo("/repos").Get(user.ListOrgRepos).
  1316. Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
  1317. m.Group("/members", func() {
  1318. m.Get("", reqToken(), org.ListMembers)
  1319. m.Combo("/{username}").Get(reqToken(), org.IsMember).
  1320. Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
  1321. })
  1322. m.Group("/actions/secrets", func() {
  1323. m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
  1324. m.Combo("/{secretname}").
  1325. Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
  1326. Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
  1327. })
  1328. m.Group("/public_members", func() {
  1329. m.Get("", org.ListPublicMembers)
  1330. m.Combo("/{username}").Get(org.IsPublicMember).
  1331. Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
  1332. Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
  1333. })
  1334. m.Group("/teams", func() {
  1335. m.Get("", reqToken(), org.ListTeams)
  1336. m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
  1337. m.Get("/search", reqToken(), org.SearchTeam)
  1338. }, reqOrgMembership())
  1339. m.Group("/labels", func() {
  1340. m.Get("", org.ListLabels)
  1341. m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
  1342. m.Combo("/{id}").Get(reqToken(), org.GetLabel).
  1343. Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
  1344. Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
  1345. })
  1346. m.Group("/hooks", func() {
  1347. m.Combo("").Get(org.ListHooks).
  1348. Post(bind(api.CreateHookOption{}), org.CreateHook)
  1349. m.Combo("/{id}").Get(org.GetHook).
  1350. Patch(bind(api.EditHookOption{}), org.EditHook).
  1351. Delete(org.DeleteHook)
  1352. }, reqToken(), reqOrgOwnership(), reqWebhooksEnabled())
  1353. m.Group("/avatar", func() {
  1354. m.Post("", bind(api.UpdateUserAvatarOption{}), org.UpdateAvatar)
  1355. m.Delete("", org.DeleteAvatar)
  1356. }, reqToken(), reqOrgOwnership())
  1357. m.Get("/activities/feeds", org.ListOrgActivityFeeds)
  1358. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
  1359. m.Group("/teams/{teamid}", func() {
  1360. m.Combo("").Get(reqToken(), org.GetTeam).
  1361. Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
  1362. Delete(reqToken(), reqOrgOwnership(), org.DeleteTeam)
  1363. m.Group("/members", func() {
  1364. m.Get("", reqToken(), org.GetTeamMembers)
  1365. m.Combo("/{username}").
  1366. Get(reqToken(), org.GetTeamMember).
  1367. Put(reqToken(), reqOrgOwnership(), org.AddTeamMember).
  1368. Delete(reqToken(), reqOrgOwnership(), org.RemoveTeamMember)
  1369. })
  1370. m.Group("/repos", func() {
  1371. m.Get("", reqToken(), org.GetTeamRepos)
  1372. m.Combo("/{org}/{reponame}").
  1373. Put(reqToken(), org.AddTeamRepository).
  1374. Delete(reqToken(), org.RemoveTeamRepository).
  1375. Get(reqToken(), org.GetTeamRepo)
  1376. })
  1377. m.Get("/activities/feeds", org.ListTeamActivityFeeds)
  1378. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership())
  1379. m.Group("/admin", func() {
  1380. m.Group("/cron", func() {
  1381. m.Get("", admin.ListCronTasks)
  1382. m.Post("/{task}", admin.PostCronTask)
  1383. })
  1384. m.Get("/orgs", admin.GetAllOrgs)
  1385. m.Group("/users", func() {
  1386. m.Get("", admin.SearchUsers)
  1387. m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
  1388. m.Group("/{username}", func() {
  1389. m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
  1390. Delete(admin.DeleteUser)
  1391. m.Group("/keys", func() {
  1392. m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
  1393. m.Delete("/{id}", admin.DeleteUserPublicKey)
  1394. })
  1395. m.Get("/orgs", org.ListUserOrgs)
  1396. m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
  1397. m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
  1398. m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
  1399. }, context_service.UserAssignmentAPI())
  1400. })
  1401. m.Group("/emails", func() {
  1402. m.Get("", admin.GetAllEmails)
  1403. m.Get("/search", admin.SearchEmail)
  1404. })
  1405. m.Group("/unadopted", func() {
  1406. m.Get("", admin.ListUnadoptedRepositories)
  1407. m.Post("/{username}/{reponame}", admin.AdoptRepository)
  1408. m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository)
  1409. })
  1410. m.Group("/hooks", func() {
  1411. m.Combo("").Get(admin.ListHooks).
  1412. Post(bind(api.CreateHookOption{}), admin.CreateHook)
  1413. m.Combo("/{id}").Get(admin.GetHook).
  1414. Patch(bind(api.EditHookOption{}), admin.EditHook).
  1415. Delete(admin.DeleteHook)
  1416. })
  1417. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
  1418. m.Group("/topics", func() {
  1419. m.Get("/search", repo.TopicSearch)
  1420. }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
  1421. }, sudo())
  1422. return m
  1423. }
  1424. func securityHeaders() func(http.Handler) http.Handler {
  1425. return func(next http.Handler) http.Handler {
  1426. return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  1427. // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
  1428. // http://stackoverflow.com/a/3146618/244009
  1429. resp.Header().Set("x-content-type-options", "nosniff")
  1430. next.ServeHTTP(resp, req)
  1431. })
  1432. }
  1433. }