forgejo/services/context/api.go

460 lines
13 KiB
Go
Raw Normal View History

2016-03-14 00:37:44 +03:00
// Copyright 2016 The Gogs Authors. All rights reserved.
2019-03-19 05:29:43 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2016-03-14 00:37:44 +03:00
package context
import (
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
"context"
"fmt"
"net/http"
2019-06-26 11:51:32 +03:00
"net/url"
"strings"
[GITEA] API commentAssignment() to verify the id belongs Instead of repeating the tests that verify the ID of a comment is related to the repository of the API endpoint, add the middleware function commentAssignment() to assign ctx.Comment if the ID of the comment is verified to be related to the repository. There already are integration tests for cases of potential unrelated comment IDs that cover some of the modified endpoints which covers the commentAssignment() function logic. * TestAPICommentReactions - GetIssueCommentReactions * TestAPICommentReactions - PostIssueCommentReaction * TestAPICommentReactions - DeleteIssueCommentReaction * TestAPIEditComment - EditIssueComment * TestAPIDeleteComment - DeleteIssueComment * TestAPIGetCommentAttachment - GetIssueCommentAttachment The other modified endpoints do not have tests to verify cases of potential unrelated comment IDs. They no longer need to because they no longer implement the logic to enforce this. They however all have integration tests that verify the commentAssignment() they now rely on does not introduce a regression. * TestAPIGetComment - GetIssueComment * TestAPIListCommentAttachments - ListIssueCommentAttachments * TestAPICreateCommentAttachment - CreateIssueCommentAttachment * TestAPIEditCommentAttachment - EditIssueCommentAttachment * TestAPIDeleteCommentAttachment - DeleteIssueCommentAttachment (cherry picked from commit d414376d749041da1be288c02fdaa24fddeafd5c) (cherry picked from commit 09db07aeaed167edc66cb832b0aa54b31d14f0d8) (cherry picked from commit f44830c3cba0b9416505a2b0b560cfa096ffeb7c) Conflicts: modules/context/api.go https://codeberg.org/forgejo/forgejo/pulls/2249 (cherry picked from commit 9d1bf7be15420ce4ca6e92a8bd048d483172de3b)
2024-01-11 13:54:16 +03:00
issues_model "code.gitea.io/gitea/models/issues"
feat(quota): Humble beginnings of a quota engine This is an implementation of a quota engine, and the API routes to manage its settings. This does *not* contain any enforcement code: this is just the bedrock, the engine itself. The goal of the engine is to be flexible and future proof: to be nimble enough to build on it further, without having to rewrite large parts of it. It might feel a little more complicated than necessary, because the goal was to be able to support scenarios only very few Forgejo instances need, scenarios the vast majority of mostly smaller instances simply do not care about. The goal is to support both big and small, and for that, we need a solid, flexible foundation. There are thee big parts to the engine: counting quota use, setting limits, and evaluating whether the usage is within the limits. Sounds simple on paper, less so in practice! Quota counting ============== Quota is counted based on repo ownership, whenever possible, because repo owners are in ultimate control over the resources they use: they can delete repos, attachments, everything, even if they don't *own* those themselves. They can clean up, and will always have the permission and access required to do so. Would we count quota based on the owning user, that could lead to situations where a user is unable to free up space, because they uploaded a big attachment to a repo that has been taken private since. It's both more fair, and much safer to count quota against repo owners. This means that if user A uploads an attachment to an issue opened against organization O, that will count towards the quota of organization O, rather than user A. One's quota usage stats can be queried using the `/user/quota` API endpoint. To figure out what's eating into it, the `/user/repos?order_by=size`, `/user/quota/attachments`, `/user/quota/artifacts`, and `/user/quota/packages` endpoints should be consulted. There's also `/user/quota/check?subject=<...>` to check whether the signed-in user is within a particular quota limit. Quotas are counted based on sizes stored in the database. Setting quota limits ==================== There are different "subjects" one can limit usage for. At this time, only size-based limits are implemented, which are: - `size:all`: As the name would imply, the total size of everything Forgejo tracks. - `size:repos:all`: The total size of all repositories (not including LFS). - `size:repos:public`: The total size of all public repositories (not including LFS). - `size:repos:private`: The total size of all private repositories (not including LFS). - `size:git:all`: The total size of all git data (including all repositories, and LFS). - `size:git:lfs`: The size of all git LFS data (either in private or public repos). - `size:assets:all`: The size of all assets tracked by Forgejo. - `size:assets:attachments:all`: The size of all kinds of attachments tracked by Forgejo. - `size:assets:attachments:issues`: Size of all attachments attached to issues, including issue comments. - `size:assets:attachments:releases`: Size of all attachments attached to releases. This does *not* include automatically generated archives. - `size:assets:artifacts`: Size of all Action artifacts. - `size:assets:packages:all`: Size of all Packages. - `size:wiki`: Wiki size Wiki size is currently not tracked, and the engine will always deem it within quota. These subjects are built into Rules, which set a limit on *all* subjects within a rule. Thus, we can create a rule that says: "1Gb limit on all release assets, all packages, and git LFS, combined". For a rule to stand, the total sum of all subjects must be below the rule's limit. Rules are in turn collected into groups. A group is just a name, and a list of rules. For a group to stand, all of its rules must stand. Thus, if we have a group with two rules, one that sets a combined 1Gb limit on release assets, all packages, and git LFS, and another rule that sets a 256Mb limit on packages, if the user has 512Mb of packages, the group will not stand, because the second rule deems it over quota. Similarly, if the user has only 128Mb of packages, but 900Mb of release assets, the group will not stand, because the combined size of packages and release assets is over the 1Gb limit of the first rule. Groups themselves are collected into Group Lists. A group list stands when *any* of the groups within stand. This allows an administrator to set conservative defaults, but then place select users into additional groups that increase some aspect of their limits. To top it off, it is possible to set the default quota groups a user belongs to in `app.ini`. If there's no explicit assignment, the engine will use the default groups. This makes it possible to avoid having to assign each and every user a list of quota groups, and only those need to be explicitly assigned who need a different set of groups than the defaults. If a user has any quota groups assigned to them, the default list will not be considered for them. The management APIs =================== This commit contains the engine itself, its unit tests, and the quota management APIs. It does not contain any enforcement. The APIs are documented in-code, and in the swagger docs, and the integration tests can serve as an example on how to use them. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-07-06 11:25:41 +03:00
quota_model "code.gitea.io/gitea/models/quota"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
mc "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
web_types "code.gitea.io/gitea/modules/web/types"
"code.forgejo.org/go-chi/cache"
2016-03-14 00:37:44 +03:00
)
// APIContext is a specific context for API service
2016-03-14 00:37:44 +03:00
type APIContext struct {
*Base
Cache cache.Cache
Doer *user_model.User // current signed-in user
IsSigned bool
IsBasicAuth bool
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
feat(quota): Humble beginnings of a quota engine This is an implementation of a quota engine, and the API routes to manage its settings. This does *not* contain any enforcement code: this is just the bedrock, the engine itself. The goal of the engine is to be flexible and future proof: to be nimble enough to build on it further, without having to rewrite large parts of it. It might feel a little more complicated than necessary, because the goal was to be able to support scenarios only very few Forgejo instances need, scenarios the vast majority of mostly smaller instances simply do not care about. The goal is to support both big and small, and for that, we need a solid, flexible foundation. There are thee big parts to the engine: counting quota use, setting limits, and evaluating whether the usage is within the limits. Sounds simple on paper, less so in practice! Quota counting ============== Quota is counted based on repo ownership, whenever possible, because repo owners are in ultimate control over the resources they use: they can delete repos, attachments, everything, even if they don't *own* those themselves. They can clean up, and will always have the permission and access required to do so. Would we count quota based on the owning user, that could lead to situations where a user is unable to free up space, because they uploaded a big attachment to a repo that has been taken private since. It's both more fair, and much safer to count quota against repo owners. This means that if user A uploads an attachment to an issue opened against organization O, that will count towards the quota of organization O, rather than user A. One's quota usage stats can be queried using the `/user/quota` API endpoint. To figure out what's eating into it, the `/user/repos?order_by=size`, `/user/quota/attachments`, `/user/quota/artifacts`, and `/user/quota/packages` endpoints should be consulted. There's also `/user/quota/check?subject=<...>` to check whether the signed-in user is within a particular quota limit. Quotas are counted based on sizes stored in the database. Setting quota limits ==================== There are different "subjects" one can limit usage for. At this time, only size-based limits are implemented, which are: - `size:all`: As the name would imply, the total size of everything Forgejo tracks. - `size:repos:all`: The total size of all repositories (not including LFS). - `size:repos:public`: The total size of all public repositories (not including LFS). - `size:repos:private`: The total size of all private repositories (not including LFS). - `size:git:all`: The total size of all git data (including all repositories, and LFS). - `size:git:lfs`: The size of all git LFS data (either in private or public repos). - `size:assets:all`: The size of all assets tracked by Forgejo. - `size:assets:attachments:all`: The size of all kinds of attachments tracked by Forgejo. - `size:assets:attachments:issues`: Size of all attachments attached to issues, including issue comments. - `size:assets:attachments:releases`: Size of all attachments attached to releases. This does *not* include automatically generated archives. - `size:assets:artifacts`: Size of all Action artifacts. - `size:assets:packages:all`: Size of all Packages. - `size:wiki`: Wiki size Wiki size is currently not tracked, and the engine will always deem it within quota. These subjects are built into Rules, which set a limit on *all* subjects within a rule. Thus, we can create a rule that says: "1Gb limit on all release assets, all packages, and git LFS, combined". For a rule to stand, the total sum of all subjects must be below the rule's limit. Rules are in turn collected into groups. A group is just a name, and a list of rules. For a group to stand, all of its rules must stand. Thus, if we have a group with two rules, one that sets a combined 1Gb limit on release assets, all packages, and git LFS, and another rule that sets a 256Mb limit on packages, if the user has 512Mb of packages, the group will not stand, because the second rule deems it over quota. Similarly, if the user has only 128Mb of packages, but 900Mb of release assets, the group will not stand, because the combined size of packages and release assets is over the 1Gb limit of the first rule. Groups themselves are collected into Group Lists. A group list stands when *any* of the groups within stand. This allows an administrator to set conservative defaults, but then place select users into additional groups that increase some aspect of their limits. To top it off, it is possible to set the default quota groups a user belongs to in `app.ini`. If there's no explicit assignment, the engine will use the default groups. This makes it possible to avoid having to assign each and every user a list of quota groups, and only those need to be explicitly assigned who need a different set of groups than the defaults. If a user has any quota groups assigned to them, the default list will not be considered for them. The management APIs =================== This commit contains the engine itself, its unit tests, and the quota management APIs. It does not contain any enforcement. The APIs are documented in-code, and in the swagger docs, and the integration tests can serve as an example on how to use them. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-07-06 11:25:41 +03:00
Repo *Repository
Comment *issues_model.Comment
Org *APIOrganization
Package *Package
QuotaGroup *quota_model.Group
QuotaRule *quota_model.Rule
PublicOnly bool // Whether the request is for a public endpoint
2016-03-14 00:37:44 +03:00
}
func init() {
web.RegisterResponseStatusProvider[*APIContext](func(req *http.Request) web_types.ResponseStatusProvider {
return req.Context().Value(apiContextKey).(*APIContext)
})
}
// Currently, we have the following common fields in error response:
// * message: the message for end users (it shouldn't be used for error type detection)
// if we need to indicate some errors, we should introduce some new fields like ErrorCode or ErrorType
// * url: the swagger document URL
type APIError struct {
Message string `json:"message"`
URL string `json:"url"`
}
// APIError is error format response
// swagger:response error
type swaggerAPIError struct {
// in:body
Body APIError `json:"body"`
}
type APIValidationError struct {
Message string `json:"message"`
URL string `json:"url"`
}
// APIValidationError is error format response related to input validation
// swagger:response validationError
type swaggerAPIValidationError struct {
// in:body
Body APIValidationError `json:"body"`
}
type APIInvalidTopicsError struct {
Message string `json:"message"`
InvalidTopics []string `json:"invalidTopics"`
}
// APIInvalidTopicsError is error format response to invalid topics
// swagger:response invalidTopicsError
type swaggerAPIInvalidTopicsError struct {
// in:body
Body APIInvalidTopicsError `json:"body"`
}
// APIEmpty is an empty response
// swagger:response empty
type APIEmpty struct{}
type APIUnauthorizedError struct {
APIError
}
// APIUnauthorizedError is a unauthorized error response
// swagger:response unauthorized
type swaggerAPUnauthorizedError struct {
// in:body
Body APIUnauthorizedError `json:"body"`
}
type APIForbiddenError struct {
APIError
}
// APIForbiddenError is a forbidden error response
// swagger:response forbidden
type swaggerAPIForbiddenError struct {
// in:body
Body APIForbiddenError `json:"body"`
}
type APINotFound struct {
Message string `json:"message"`
URL string `json:"url"`
Errors []string `json:"errors"`
}
// APINotFound is a not found error response
// swagger:response notFound
type swaggerAPINotFound struct {
// in:body
Body APINotFound `json:"body"`
}
// APIConflict is a conflict empty response
// swagger:response conflict
type APIConflict struct{}
// APIRedirect is a redirect response
// swagger:response redirect
type APIRedirect struct{}
// APIString is a string response
// swagger:response string
type APIString string
type APIRepoArchivedError struct {
APIError
}
// APIRepoArchivedError is an error that is raised when an archived repo should be modified
// swagger:response repoArchivedError
type swaggerAPIRepoArchivedError struct {
// in:body
Body APIRepoArchivedError `json:"body"`
}
// ServerError responds with error message, status is 500
func (ctx *APIContext) ServerError(title string, err error) {
ctx.Error(http.StatusInternalServerError, title, err)
}
// Error responds with an error message to client with given obj as the message.
// If status is 500, also it prints error to log.
func (ctx *APIContext) Error(status int, title string, obj any) {
var message string
if err, ok := obj.(error); ok {
message = err.Error()
} else {
message = fmt.Sprintf("%s", obj)
}
if status == http.StatusInternalServerError {
log.ErrorWithSkip(1, "%s: %s", title, message)
if setting.IsProd && !(ctx.Doer != nil && ctx.Doer.IsAdmin) {
message = ""
}
}
ctx.JSON(status, APIError{
Message: message,
2019-06-13 00:07:24 +03:00
URL: setting.API.SwaggerURL,
})
}
// InternalServerError responds with an error message to the client with the error as a message
// and the file and line of the caller.
func (ctx *APIContext) InternalServerError(err error) {
log.ErrorWithSkip(1, "InternalServerError: %v", err)
var message string
if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) {
message = err.Error()
}
ctx.JSON(http.StatusInternalServerError, APIError{
Message: message,
URL: setting.API.SwaggerURL,
})
}
type apiContextKeyType struct{}
var apiContextKey = apiContextKeyType{}
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
// GetAPIContext returns a context for API routes
func GetAPIContext(req *http.Request) *APIContext {
return req.Context().Value(apiContextKey).(*APIContext)
}
2019-06-26 11:51:32 +03:00
func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
page := NewPagination(total, pageSize, curPage, 0)
paginater := page.Paginater
links := make([]string, 0, 4)
2019-06-26 11:51:32 +03:00
if paginater.HasNext() {
2019-06-26 11:51:32 +03:00
u := *curURL
queries := u.Query()
queries.Set("page", fmt.Sprintf("%d", paginater.Next()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"next\"", setting.AppURL, u.RequestURI()[1:]))
}
if !paginater.IsLast() {
2019-06-26 11:51:32 +03:00
u := *curURL
queries := u.Query()
queries.Set("page", fmt.Sprintf("%d", paginater.TotalPages()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"last\"", setting.AppURL, u.RequestURI()[1:]))
}
if !paginater.IsFirst() {
2019-06-26 11:51:32 +03:00
u := *curURL
queries := u.Query()
queries.Set("page", "1")
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"first\"", setting.AppURL, u.RequestURI()[1:]))
}
if paginater.HasPrevious() {
2019-06-26 11:51:32 +03:00
u := *curURL
queries := u.Query()
queries.Set("page", fmt.Sprintf("%d", paginater.Previous()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"prev\"", setting.AppURL, u.RequestURI()[1:]))
}
2019-06-26 11:51:32 +03:00
return links
}
// SetLinkHeader sets pagination link header by given total number and page size.
func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
links := genAPILinks(ctx.Req.URL, total, pageSize, ctx.FormInt("page"))
if len(links) > 0 {
2021-12-15 09:59:57 +03:00
ctx.RespHeader().Set("Link", strings.Join(links, ","))
ctx.AppendAccessControlExposeHeaders("Link")
}
}
// APIContexter returns apicontext as middleware
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
func APIContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
base, baseCleanUp := NewBaseContext(w, req)
ctx := &APIContext{
Base: base,
Cache: mc.GetCache(),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &APIOrganization{},
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
}
defer baseCleanUp()
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
ctx.Base.AppendContextValue(apiContextKey, ctx)
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
ctx.InternalServerError(err)
return
}
}
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
next.ServeHTTP(ctx.Resp, ctx.Req)
})
2016-03-14 00:37:44 +03:00
}
}
2016-11-15 01:33:58 +03:00
2019-03-19 05:29:43 +03:00
// NotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice
func (ctx *APIContext) NotFound(objs ...any) {
message := ctx.Locale.TrString("error.not_found")
errors := make([]string, 0)
2019-03-19 05:29:43 +03:00
for _, obj := range objs {
// Ignore nil
if obj == nil {
continue
}
2019-03-19 05:29:43 +03:00
if err, ok := obj.(error); ok {
errors = append(errors, err.Error())
} else {
message = obj.(string)
}
}
ctx.JSON(http.StatusNotFound, APINotFound{
Message: message,
URL: setting.API.SwaggerURL,
Errors: errors,
2019-03-19 05:29:43 +03:00
})
}
// ReferencesGitRepo injects the GitRepo into the Context
// you can optional skip the IsEmpty check
func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) (cancel context.CancelFunc) {
return func(ctx *APIContext) (cancel context.CancelFunc) {
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty && !(len(allowEmpty) != 0 && allowEmpty[0]) {
return nil
}
// For API calls.
if ctx.Repo.GitRepo == nil {
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
return cancel
}
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
return func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
_ = ctx.Repo.GitRepo.Close()
}
}
}
return cancel
}
}
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
func RepoRefForAPI(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req)
if ctx.Repo.GitRepo == nil {
ctx.InternalServerError(fmt.Errorf("no open git repo"))
return
}
if ref := ctx.FormTrim("ref"); len(ref) > 0 {
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
}
return
}
ctx.Repo.Commit = commit
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
ctx.Repo.TreePath = ctx.Params("*")
next.ServeHTTP(w, req)
return
}
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
var err error
if ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if ctx.Repo.GitRepo.IsTagExist(refName) {
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if len(refName) == ctx.Repo.GetObjectFormat().FullLength() {
ctx.Repo.CommitID = refName
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil {
ctx.NotFound("GetCommit", err)
return
}
} else {
ctx.NotFound(fmt.Errorf("not exist: '%s'", ctx.Params("*")))
return
}
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 18:36:53 +03:00
next.ServeHTTP(w, req)
})
}
// HasAPIError returns true if error occurs in form validation.
func (ctx *APIContext) HasAPIError() bool {
hasErr, ok := ctx.Data["HasError"]
if !ok {
return false
}
return hasErr.(bool)
}
// GetErrMsg returns error message in form validation.
func (ctx *APIContext) GetErrMsg() string {
msg, _ := ctx.Data["ErrorMsg"].(string)
if msg == "" {
msg = "invalid form data"
}
return msg
}
// NotFoundOrServerError use error check function to determine if the error
// is about not found. It responds with 404 status code for not found error,
// or error context description for logging purpose of 500 server error.
func (ctx *APIContext) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) {
if errCheck(logErr) {
ctx.JSON(http.StatusNotFound, nil)
return
}
ctx.Error(http.StatusInternalServerError, "NotFoundOrServerError", logMsg)
}
// IsUserSiteAdmin returns true if current user is a site admin
func (ctx *APIContext) IsUserSiteAdmin() bool {
return ctx.IsSigned && ctx.Doer.IsAdmin
}
// IsUserRepoAdmin returns true if current user is admin in current repo
func (ctx *APIContext) IsUserRepoAdmin() bool {
return ctx.Repo.IsAdmin()
}
// IsUserRepoWriter returns true if current user has write privilege in current repo
func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool {
for _, unitType := range unitTypes {
if ctx.Repo.CanWrite(unitType) {
return true
}
}
return false
}