Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple the different contexts from each other #24786

Merged
merged 14 commits into from
May 21, 2023
115 changes: 83 additions & 32 deletions modules/context/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,32 @@ import (

"code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/cache"
"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/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"

"gitea.com/go-chi/cache"
)

// APIContext is a specific context for API service
type APIContext struct {
*Context
Org *APIOrganization
*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

Repo *Repository
Org *APIOrganization
Package *Package
}

// Currently, we have the following common fields in error response:
Expand Down Expand Up @@ -128,11 +142,6 @@ type apiContextKeyType struct{}

var apiContextKey = apiContextKeyType{}

// WithAPIContext set up api context in request
func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
return req.WithContext(context.WithValue(req.Context(), apiContextKey, ctx))
}

// GetAPIContext returns a context for API routes
func GetAPIContext(req *http.Request) *APIContext {
return req.Context().Value(apiContextKey).(*APIContext)
Expand Down Expand Up @@ -195,21 +204,21 @@ func (ctx *APIContext) CheckForOTP() {
}

otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
twofa, err := auth.GetTwoFactorByUID(ctx.Context.Doer.ID)
twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID)
if err != nil {
if auth.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
ctx.Context.Error(http.StatusInternalServerError)
ctx.Error(http.StatusInternalServerError, "GetTwoFactorByUID", err)
return
}
ok, err := twofa.ValidateTOTP(otpHeader)
if err != nil {
ctx.Context.Error(http.StatusInternalServerError)
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err)
return
}
if !ok {
ctx.Context.Error(http.StatusUnauthorized)
ctx.Error(http.StatusUnauthorized, "", nil)
return
}
}
Expand All @@ -218,23 +227,17 @@ func (ctx *APIContext) CheckForOTP() {
func APIContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
locale := middleware.Locale(w, req)
ctx := APIContext{
Context: &Context{
Resp: NewResponse(w),
Data: middleware.GetContextData(req.Context()),
Locale: locale,
Cache: cache.GetCache(),
Repo: &Repository{
PullRequest: &PullRequest{},
},
Org: &Organization{},
},
Org: &APIOrganization{},
base, baseCleanUp := NewBaseContext(w, req)
ctx := &APIContext{
Base: base,
Cache: mc.GetCache(),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &APIOrganization{},
}
defer ctx.Close()
defer baseCleanUp()

ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
ctx.Base.AppendContextValue(apiContextKey, ctx)
ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })

// 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") {
Expand All @@ -247,8 +250,6 @@ func APIContexter() func(http.Handler) http.Handler {
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)

ctx.Data["Context"] = &ctx

next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
Expand Down Expand Up @@ -301,7 +302,7 @@ func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) (cancel context
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()
_ = ctx.Repo.GitRepo.Close()
}
}
}
Expand Down Expand Up @@ -337,7 +338,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
}

var err error
refName := getRefName(ctx.Context, RepoRefAny)
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)

if ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
Expand Down Expand Up @@ -368,3 +369,53 @@ func RepoRefForAPI(next http.Handler) http.Handler {
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
}
Loading