Skip to content

Commit 6b33152

Browse files
authored
Decouple the different contexts from each other (#24786)
Replace #16455 Close #21803 Mixing different Gitea contexts together causes some problems: 1. Unable to respond proper content when error occurs, eg: Web should respond HTML while API should respond JSON 2. Unclear dependency, eg: it's unclear when Context is used in APIContext, which fields should be initialized, which methods are necessary. To make things clear, this PR introduces a Base context, it only provides basic Req/Resp/Data features. This PR mainly moves code. There are still many legacy problems and TODOs in code, leave unrelated changes to future PRs.
1 parent 6ba4f89 commit 6b33152

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+882
-778
lines changed

modules/context/api.go

+83-32
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,32 @@ import (
1313

1414
"code.gitea.io/gitea/models/auth"
1515
repo_model "code.gitea.io/gitea/models/repo"
16-
"code.gitea.io/gitea/modules/cache"
16+
"code.gitea.io/gitea/models/unit"
17+
user_model "code.gitea.io/gitea/models/user"
18+
mc "code.gitea.io/gitea/modules/cache"
1719
"code.gitea.io/gitea/modules/git"
1820
"code.gitea.io/gitea/modules/httpcache"
1921
"code.gitea.io/gitea/modules/log"
2022
"code.gitea.io/gitea/modules/setting"
21-
"code.gitea.io/gitea/modules/web/middleware"
23+
24+
"gitea.com/go-chi/cache"
2225
)
2326

2427
// APIContext is a specific context for API service
2528
type APIContext struct {
26-
*Context
27-
Org *APIOrganization
29+
*Base
30+
31+
Cache cache.Cache
32+
33+
Doer *user_model.User // current signed-in user
34+
IsSigned bool
35+
IsBasicAuth bool
36+
37+
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
38+
39+
Repo *Repository
40+
Org *APIOrganization
41+
Package *Package
2842
}
2943

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

129143
var apiContextKey = apiContextKeyType{}
130144

131-
// WithAPIContext set up api context in request
132-
func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
133-
return req.WithContext(context.WithValue(req.Context(), apiContextKey, ctx))
134-
}
135-
136145
// GetAPIContext returns a context for API routes
137146
func GetAPIContext(req *http.Request) *APIContext {
138147
return req.Context().Value(apiContextKey).(*APIContext)
@@ -195,21 +204,21 @@ func (ctx *APIContext) CheckForOTP() {
195204
}
196205

197206
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
198-
twofa, err := auth.GetTwoFactorByUID(ctx.Context.Doer.ID)
207+
twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID)
199208
if err != nil {
200209
if auth.IsErrTwoFactorNotEnrolled(err) {
201210
return // No 2FA enrollment for this user
202211
}
203-
ctx.Context.Error(http.StatusInternalServerError)
212+
ctx.Error(http.StatusInternalServerError, "GetTwoFactorByUID", err)
204213
return
205214
}
206215
ok, err := twofa.ValidateTOTP(otpHeader)
207216
if err != nil {
208-
ctx.Context.Error(http.StatusInternalServerError)
217+
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err)
209218
return
210219
}
211220
if !ok {
212-
ctx.Context.Error(http.StatusUnauthorized)
221+
ctx.Error(http.StatusUnauthorized, "", nil)
213222
return
214223
}
215224
}
@@ -218,23 +227,17 @@ func (ctx *APIContext) CheckForOTP() {
218227
func APIContexter() func(http.Handler) http.Handler {
219228
return func(next http.Handler) http.Handler {
220229
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
221-
locale := middleware.Locale(w, req)
222-
ctx := APIContext{
223-
Context: &Context{
224-
Resp: NewResponse(w),
225-
Data: middleware.GetContextData(req.Context()),
226-
Locale: locale,
227-
Cache: cache.GetCache(),
228-
Repo: &Repository{
229-
PullRequest: &PullRequest{},
230-
},
231-
Org: &Organization{},
232-
},
233-
Org: &APIOrganization{},
230+
base, baseCleanUp := NewBaseContext(w, req)
231+
ctx := &APIContext{
232+
Base: base,
233+
Cache: mc.GetCache(),
234+
Repo: &Repository{PullRequest: &PullRequest{}},
235+
Org: &APIOrganization{},
234236
}
235-
defer ctx.Close()
237+
defer baseCleanUp()
236238

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

239242
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
240243
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
@@ -247,8 +250,6 @@ func APIContexter() func(http.Handler) http.Handler {
247250
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
248251
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
249252

250-
ctx.Data["Context"] = &ctx
251-
252253
next.ServeHTTP(ctx.Resp, ctx.Req)
253254
})
254255
}
@@ -301,7 +302,7 @@ func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) (cancel context
301302
return func() {
302303
// If it's been set to nil then assume someone else has closed it.
303304
if ctx.Repo.GitRepo != nil {
304-
ctx.Repo.GitRepo.Close()
305+
_ = ctx.Repo.GitRepo.Close()
305306
}
306307
}
307308
}
@@ -337,7 +338,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
337338
}
338339

339340
var err error
340-
refName := getRefName(ctx.Context, RepoRefAny)
341+
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
341342

342343
if ctx.Repo.GitRepo.IsBranchExist(refName) {
343344
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
@@ -368,3 +369,53 @@ func RepoRefForAPI(next http.Handler) http.Handler {
368369
next.ServeHTTP(w, req)
369370
})
370371
}
372+
373+
// HasAPIError returns true if error occurs in form validation.
374+
func (ctx *APIContext) HasAPIError() bool {
375+
hasErr, ok := ctx.Data["HasError"]
376+
if !ok {
377+
return false
378+
}
379+
return hasErr.(bool)
380+
}
381+
382+
// GetErrMsg returns error message in form validation.
383+
func (ctx *APIContext) GetErrMsg() string {
384+
msg, _ := ctx.Data["ErrorMsg"].(string)
385+
if msg == "" {
386+
msg = "invalid form data"
387+
}
388+
return msg
389+
}
390+
391+
// NotFoundOrServerError use error check function to determine if the error
392+
// is about not found. It responds with 404 status code for not found error,
393+
// or error context description for logging purpose of 500 server error.
394+
func (ctx *APIContext) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) {
395+
if errCheck(logErr) {
396+
ctx.JSON(http.StatusNotFound, nil)
397+
return
398+
}
399+
ctx.Error(http.StatusInternalServerError, "NotFoundOrServerError", logMsg)
400+
}
401+
402+
// IsUserSiteAdmin returns true if current user is a site admin
403+
func (ctx *APIContext) IsUserSiteAdmin() bool {
404+
return ctx.IsSigned && ctx.Doer.IsAdmin
405+
}
406+
407+
// IsUserRepoAdmin returns true if current user is admin in current repo
408+
func (ctx *APIContext) IsUserRepoAdmin() bool {
409+
return ctx.Repo.IsAdmin()
410+
}
411+
412+
// IsUserRepoWriter returns true if current user has write privilege in current repo
413+
func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool {
414+
for _, unitType := range unitTypes {
415+
if ctx.Repo.CanWrite(unitType) {
416+
return true
417+
}
418+
}
419+
420+
return false
421+
}

0 commit comments

Comments
 (0)