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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package context
  6. import (
  7. "context"
  8. "fmt"
  9. "html"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/models/login"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/web/middleware"
  19. "code.gitea.io/gitea/services/auth"
  20. "gitea.com/go-chi/session"
  21. )
  22. // APIContext is a specific context for API service
  23. type APIContext struct {
  24. *Context
  25. Org *APIOrganization
  26. }
  27. // APIError is error format response
  28. // swagger:response error
  29. type APIError struct {
  30. Message string `json:"message"`
  31. URL string `json:"url"`
  32. }
  33. // APIValidationError is error format response related to input validation
  34. // swagger:response validationError
  35. type APIValidationError struct {
  36. Message string `json:"message"`
  37. URL string `json:"url"`
  38. }
  39. // APIInvalidTopicsError is error format response to invalid topics
  40. // swagger:response invalidTopicsError
  41. type APIInvalidTopicsError struct {
  42. Topics []string `json:"invalidTopics"`
  43. Message string `json:"message"`
  44. }
  45. //APIEmpty is an empty response
  46. // swagger:response empty
  47. type APIEmpty struct{}
  48. //APIForbiddenError is a forbidden error response
  49. // swagger:response forbidden
  50. type APIForbiddenError struct {
  51. APIError
  52. }
  53. //APINotFound is a not found empty response
  54. // swagger:response notFound
  55. type APINotFound struct{}
  56. //APIConflict is a conflict empty response
  57. // swagger:response conflict
  58. type APIConflict struct{}
  59. //APIRedirect is a redirect response
  60. // swagger:response redirect
  61. type APIRedirect struct{}
  62. //APIString is a string response
  63. // swagger:response string
  64. type APIString string
  65. // ServerError responds with error message, status is 500
  66. func (ctx *APIContext) ServerError(title string, err error) {
  67. ctx.Error(http.StatusInternalServerError, title, err)
  68. }
  69. // Error responds with an error message to client with given obj as the message.
  70. // If status is 500, also it prints error to log.
  71. func (ctx *APIContext) Error(status int, title string, obj interface{}) {
  72. var message string
  73. if err, ok := obj.(error); ok {
  74. message = err.Error()
  75. } else {
  76. message = fmt.Sprintf("%s", obj)
  77. }
  78. if status == http.StatusInternalServerError {
  79. log.ErrorWithSkip(1, "%s: %s", title, message)
  80. if setting.IsProd() && !(ctx.User != nil && ctx.User.IsAdmin) {
  81. message = ""
  82. }
  83. }
  84. ctx.JSON(status, APIError{
  85. Message: message,
  86. URL: setting.API.SwaggerURL,
  87. })
  88. }
  89. // InternalServerError responds with an error message to the client with the error as a message
  90. // and the file and line of the caller.
  91. func (ctx *APIContext) InternalServerError(err error) {
  92. log.ErrorWithSkip(1, "InternalServerError: %v", err)
  93. var message string
  94. if !setting.IsProd() || (ctx.User != nil && ctx.User.IsAdmin) {
  95. message = err.Error()
  96. }
  97. ctx.JSON(http.StatusInternalServerError, APIError{
  98. Message: message,
  99. URL: setting.API.SwaggerURL,
  100. })
  101. }
  102. var (
  103. apiContextKey interface{} = "default_api_context"
  104. )
  105. // WithAPIContext set up api context in request
  106. func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
  107. return req.WithContext(context.WithValue(req.Context(), apiContextKey, ctx))
  108. }
  109. // GetAPIContext returns a context for API routes
  110. func GetAPIContext(req *http.Request) *APIContext {
  111. return req.Context().Value(apiContextKey).(*APIContext)
  112. }
  113. func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
  114. page := NewPagination(total, pageSize, curPage, 0)
  115. paginater := page.Paginater
  116. links := make([]string, 0, 4)
  117. if paginater.HasNext() {
  118. u := *curURL
  119. queries := u.Query()
  120. queries.Set("page", fmt.Sprintf("%d", paginater.Next()))
  121. u.RawQuery = queries.Encode()
  122. links = append(links, fmt.Sprintf("<%s%s>; rel=\"next\"", setting.AppURL, u.RequestURI()[1:]))
  123. }
  124. if !paginater.IsLast() {
  125. u := *curURL
  126. queries := u.Query()
  127. queries.Set("page", fmt.Sprintf("%d", paginater.TotalPages()))
  128. u.RawQuery = queries.Encode()
  129. links = append(links, fmt.Sprintf("<%s%s>; rel=\"last\"", setting.AppURL, u.RequestURI()[1:]))
  130. }
  131. if !paginater.IsFirst() {
  132. u := *curURL
  133. queries := u.Query()
  134. queries.Set("page", "1")
  135. u.RawQuery = queries.Encode()
  136. links = append(links, fmt.Sprintf("<%s%s>; rel=\"first\"", setting.AppURL, u.RequestURI()[1:]))
  137. }
  138. if paginater.HasPrevious() {
  139. u := *curURL
  140. queries := u.Query()
  141. queries.Set("page", fmt.Sprintf("%d", paginater.Previous()))
  142. u.RawQuery = queries.Encode()
  143. links = append(links, fmt.Sprintf("<%s%s>; rel=\"prev\"", setting.AppURL, u.RequestURI()[1:]))
  144. }
  145. return links
  146. }
  147. // SetLinkHeader sets pagination link header by given total number and page size.
  148. func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
  149. links := genAPILinks(ctx.Req.URL, total, pageSize, ctx.FormInt("page"))
  150. if len(links) > 0 {
  151. ctx.Header().Set("Link", strings.Join(links, ","))
  152. ctx.AppendAccessControlExposeHeaders("Link")
  153. }
  154. }
  155. // SetTotalCountHeader set "X-Total-Count" header
  156. func (ctx *APIContext) SetTotalCountHeader(total int64) {
  157. ctx.Header().Set("X-Total-Count", fmt.Sprint(total))
  158. ctx.AppendAccessControlExposeHeaders("X-Total-Count")
  159. }
  160. // AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
  161. func (ctx *APIContext) AppendAccessControlExposeHeaders(names ...string) {
  162. val := ctx.Header().Get("Access-Control-Expose-Headers")
  163. if len(val) != 0 {
  164. ctx.Header().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
  165. } else {
  166. ctx.Header().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
  167. }
  168. }
  169. // RequireCSRF requires a validated a CSRF token
  170. func (ctx *APIContext) RequireCSRF() {
  171. headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
  172. formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName())
  173. if len(headerToken) > 0 || len(formValueToken) > 0 {
  174. Validate(ctx.Context, ctx.csrf)
  175. } else {
  176. ctx.Context.Error(401, "Missing CSRF token.")
  177. }
  178. }
  179. // CheckForOTP validates OTP
  180. func (ctx *APIContext) CheckForOTP() {
  181. if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
  182. return // Skip 2FA
  183. }
  184. otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
  185. twofa, err := login.GetTwoFactorByUID(ctx.Context.User.ID)
  186. if err != nil {
  187. if login.IsErrTwoFactorNotEnrolled(err) {
  188. return // No 2FA enrollment for this user
  189. }
  190. ctx.Context.Error(http.StatusInternalServerError)
  191. return
  192. }
  193. ok, err := twofa.ValidateTOTP(otpHeader)
  194. if err != nil {
  195. ctx.Context.Error(http.StatusInternalServerError)
  196. return
  197. }
  198. if !ok {
  199. ctx.Context.Error(401)
  200. return
  201. }
  202. }
  203. // APIAuth converts auth.Auth as a middleware
  204. func APIAuth(authMethod auth.Method) func(*APIContext) {
  205. return func(ctx *APIContext) {
  206. // Get user from session if logged in.
  207. ctx.User = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
  208. if ctx.User != nil {
  209. ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == new(auth.Basic).Name()
  210. ctx.IsSigned = true
  211. ctx.Data["IsSigned"] = ctx.IsSigned
  212. ctx.Data["SignedUser"] = ctx.User
  213. ctx.Data["SignedUserID"] = ctx.User.ID
  214. ctx.Data["SignedUserName"] = ctx.User.Name
  215. ctx.Data["IsAdmin"] = ctx.User.IsAdmin
  216. } else {
  217. ctx.Data["SignedUserID"] = int64(0)
  218. ctx.Data["SignedUserName"] = ""
  219. }
  220. }
  221. }
  222. // APIContexter returns apicontext as middleware
  223. func APIContexter() func(http.Handler) http.Handler {
  224. var csrfOpts = getCsrfOpts()
  225. return func(next http.Handler) http.Handler {
  226. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  227. var locale = middleware.Locale(w, req)
  228. var ctx = APIContext{
  229. Context: &Context{
  230. Resp: NewResponse(w),
  231. Data: map[string]interface{}{},
  232. Locale: locale,
  233. Session: session.GetSession(req),
  234. Repo: &Repository{
  235. PullRequest: &PullRequest{},
  236. },
  237. Org: &Organization{},
  238. },
  239. Org: &APIOrganization{},
  240. }
  241. ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
  242. ctx.csrf = Csrfer(csrfOpts, ctx.Context)
  243. // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
  244. if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
  245. if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
  246. ctx.InternalServerError(err)
  247. return
  248. }
  249. }
  250. ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
  251. ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
  252. next.ServeHTTP(ctx.Resp, ctx.Req)
  253. // Handle adding signedUserName to the context for the AccessLogger
  254. usernameInterface := ctx.Data["SignedUserName"]
  255. identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
  256. if usernameInterface != nil && identityPtrInterface != nil {
  257. username := usernameInterface.(string)
  258. identityPtr := identityPtrInterface.(*string)
  259. if identityPtr != nil && username != "" {
  260. *identityPtr = username
  261. }
  262. }
  263. })
  264. }
  265. }
  266. // ReferencesGitRepo injects the GitRepo into the Context
  267. func ReferencesGitRepo(allowEmpty bool) func(http.Handler) http.Handler {
  268. return func(next http.Handler) http.Handler {
  269. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  270. ctx := GetAPIContext(req)
  271. // Empty repository does not have reference information.
  272. if !allowEmpty && ctx.Repo.Repository.IsEmpty {
  273. return
  274. }
  275. // For API calls.
  276. if ctx.Repo.GitRepo == nil {
  277. repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  278. gitRepo, err := git.OpenRepository(repoPath)
  279. if err != nil {
  280. ctx.Error(http.StatusInternalServerError, "RepoRef Invalid repo "+repoPath, err)
  281. return
  282. }
  283. ctx.Repo.GitRepo = gitRepo
  284. // We opened it, we should close it
  285. defer func() {
  286. // If it's been set to nil then assume someone else has closed it.
  287. if ctx.Repo.GitRepo != nil {
  288. ctx.Repo.GitRepo.Close()
  289. }
  290. }()
  291. }
  292. next.ServeHTTP(w, req)
  293. })
  294. }
  295. }
  296. // NotFound handles 404s for APIContext
  297. // String will replace message, errors will be added to a slice
  298. func (ctx *APIContext) NotFound(objs ...interface{}) {
  299. var message = "Not Found"
  300. var errors []string
  301. for _, obj := range objs {
  302. // Ignore nil
  303. if obj == nil {
  304. continue
  305. }
  306. if err, ok := obj.(error); ok {
  307. errors = append(errors, err.Error())
  308. } else {
  309. message = obj.(string)
  310. }
  311. }
  312. ctx.JSON(http.StatusNotFound, map[string]interface{}{
  313. "message": message,
  314. "documentation_url": setting.API.SwaggerURL,
  315. "errors": errors,
  316. })
  317. }
  318. // RepoRefForAPI handles repository reference names when the ref name is not explicitly given
  319. func RepoRefForAPI(next http.Handler) http.Handler {
  320. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  321. ctx := GetAPIContext(req)
  322. // Empty repository does not have reference information.
  323. if ctx.Repo.Repository.IsEmpty {
  324. return
  325. }
  326. var err error
  327. if ctx.Repo.GitRepo == nil {
  328. repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  329. ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
  330. if err != nil {
  331. ctx.InternalServerError(err)
  332. return
  333. }
  334. // We opened it, we should close it
  335. defer func() {
  336. // If it's been set to nil then assume someone else has closed it.
  337. if ctx.Repo.GitRepo != nil {
  338. ctx.Repo.GitRepo.Close()
  339. }
  340. }()
  341. }
  342. refName := getRefName(ctx.Context, RepoRefAny)
  343. if ctx.Repo.GitRepo.IsBranchExist(refName) {
  344. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  345. if err != nil {
  346. ctx.InternalServerError(err)
  347. return
  348. }
  349. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  350. } else if ctx.Repo.GitRepo.IsTagExist(refName) {
  351. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
  352. if err != nil {
  353. ctx.InternalServerError(err)
  354. return
  355. }
  356. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  357. } else if len(refName) == 40 {
  358. ctx.Repo.CommitID = refName
  359. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
  360. if err != nil {
  361. ctx.NotFound("GetCommit", err)
  362. return
  363. }
  364. } else {
  365. ctx.NotFound(fmt.Errorf("not exist: '%s'", ctx.Params("*")))
  366. return
  367. }
  368. next.ServeHTTP(w, req)
  369. })
  370. }