From 46892c01aeaae3526c7b1adf7a8bf9416f1b9c1e Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 30 Mar 2025 13:26:19 +0800 Subject: [PATCH 1/3] Add a config option to block "expensive" pages for anonymous users (#34024) Fix #33966 ``` ;; User must sign in to view anything. ;; It could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources, ;; for example: block anonymous AI crawlers from accessing repo code pages. ;; The "expensive" mode is experimental and subject to change. ;REQUIRE_SIGNIN_VIEW = false ``` # Conflicts: # routers/api/v1/api.go # tests/integration/api_org_test.go --- custom/conf/app.example.ini | 3 + modules/setting/config_provider.go | 1 + modules/setting/service.go | 16 +++- modules/setting/service_test.go | 41 +++++++-- routers/api/packages/cargo/cargo.go | 2 +- routers/api/packages/container/container.go | 4 +- routers/api/v1/api.go | 2 +- routers/common/blockexpensive.go | 90 +++++++++++++++++++ routers/common/blockexpensive_test.go | 30 +++++++ routers/install/install.go | 2 +- routers/private/serv.go | 2 +- routers/web/repo/githttp.go | 2 +- routers/web/web.go | 16 ++-- services/context/package.go | 2 +- services/packages/cargo/index.go | 2 +- templates/admin/config.tmpl | 2 +- .../api_packages_container_test.go | 2 +- .../integration/api_packages_generic_test.go | 7 +- tests/integration/git_smart_http_test.go | 2 +- tests/integration/signin_test.go | 30 +++++++ 20 files changed, 223 insertions(+), 35 deletions(-) create mode 100644 routers/common/blockexpensive.go create mode 100644 routers/common/blockexpensive_test.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b417baae8b357..2f3a5acfefbc5 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -774,6 +774,9 @@ LEVEL = Info ;ALLOW_ONLY_EXTERNAL_REGISTRATION = false ;; ;; User must sign in to view anything. +;; It could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources, +;; for example: block anonymous AI crawlers from accessing repo code pages. +;; The "expensive" mode is experimental and subject to change. ;REQUIRE_SIGNIN_VIEW = false ;; ;; Mail notification diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 3138f8a63eeda..b34751e9593c2 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -26,6 +26,7 @@ type ConfigKey interface { In(defaultVal string, candidates []string) string String() string Strings(delim string) []string + Bool() (bool, error) MustString(defaultVal string) string MustBool(defaultVal ...bool) bool diff --git a/modules/setting/service.go b/modules/setting/service.go index 8c1843eeb7548..d9535efec6ee8 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -43,7 +43,8 @@ var Service = struct { ShowRegistrationButton bool EnablePasswordSignInForm bool ShowMilestonesDashboardPage bool - RequireSignInView bool + RequireSignInViewStrict bool + BlockAnonymousAccessExpensive bool EnableNotifyMail bool EnableBasicAuth bool EnablePasskeyAuth bool @@ -159,7 +160,18 @@ func loadServiceFrom(rootCfg ConfigProvider) { Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST") Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration)) Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true) - Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() + + // boolean values are considered as "strict" + var err error + Service.RequireSignInViewStrict, err = sec.Key("REQUIRE_SIGNIN_VIEW").Bool() + if s := sec.Key("REQUIRE_SIGNIN_VIEW").String(); err != nil && s != "" { + // non-boolean value only supports "expensive" at the moment + Service.BlockAnonymousAccessExpensive = s == "expensive" + if !Service.BlockAnonymousAccessExpensive { + log.Fatal("Invalid config option: REQUIRE_SIGNIN_VIEW = %s", s) + } + } + Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true) Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true) Service.EnablePasskeyAuth = sec.Key("ENABLE_PASSKEY_AUTHENTICATION").MustBool(true) diff --git a/modules/setting/service_test.go b/modules/setting/service_test.go index 1647bcec160a1..73736b793a8db 100644 --- a/modules/setting/service_test.go +++ b/modules/setting/service_test.go @@ -7,16 +7,14 @@ import ( "testing" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "github.com/gobwas/glob" "github.com/stretchr/testify/assert" ) func TestLoadServices(t *testing.T) { - oldService := Service - defer func() { - Service = oldService - }() + defer test.MockVariableValue(&Service)() cfg, err := NewConfigProviderFromData(` [service] @@ -48,10 +46,7 @@ EMAIL_DOMAIN_BLOCKLIST = d3, *.b } func TestLoadServiceVisibilityModes(t *testing.T) { - oldService := Service - defer func() { - Service = oldService - }() + defer test.MockVariableValue(&Service)() kases := map[string]func(){ ` @@ -130,3 +125,33 @@ ALLOWED_USER_VISIBILITY_MODES = public, limit, privated }) } } + +func TestLoadServiceRequireSignInView(t *testing.T) { + defer test.MockVariableValue(&Service)() + + cfg, err := NewConfigProviderFromData(` +[service] +`) + assert.NoError(t, err) + loadServiceFrom(cfg) + assert.False(t, Service.RequireSignInViewStrict) + assert.False(t, Service.BlockAnonymousAccessExpensive) + + cfg, err = NewConfigProviderFromData(` +[service] +REQUIRE_SIGNIN_VIEW = true +`) + assert.NoError(t, err) + loadServiceFrom(cfg) + assert.True(t, Service.RequireSignInViewStrict) + assert.False(t, Service.BlockAnonymousAccessExpensive) + + cfg, err = NewConfigProviderFromData(` +[service] +REQUIRE_SIGNIN_VIEW = expensive +`) + assert.NoError(t, err) + loadServiceFrom(cfg) + assert.False(t, Service.RequireSignInViewStrict) + assert.True(t, Service.BlockAnonymousAccessExpensive) +} diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go index 3d8407e6b6000..c1755dc1e84d5 100644 --- a/routers/api/packages/cargo/cargo.go +++ b/routers/api/packages/cargo/cargo.go @@ -51,7 +51,7 @@ func apiError(ctx *context.Context, status int, obj any) { // https://rust-lang.github.io/rfcs/2789-sparse-index.html func RepositoryConfig(ctx *context.Context) { - ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInView || ctx.Package.Owner.Visibility != structs.VisibleTypePublic)) + ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInViewStrict || ctx.Package.Owner.Visibility != structs.VisibleTypePublic)) } func EnumeratePackageVersions(ctx *context.Context) { diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index bb14db9db79d2..6ef1655235617 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -126,7 +126,7 @@ func apiUnauthorizedError(ctx *context.Context) { // ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled) func ReqContainerAccess(ctx *context.Context) { - if ctx.Doer == nil || (setting.Service.RequireSignInView && ctx.Doer.IsGhost()) { + if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) { apiUnauthorizedError(ctx) } } @@ -152,7 +152,7 @@ func Authenticate(ctx *context.Context) { u := ctx.Doer packageScope := auth_service.GetAccessScope(ctx.Data) if u == nil { - if setting.Service.RequireSignInView { + if setting.Service.RequireSignInViewStrict { apiUnauthorizedError(ctx) return } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index d0a2bd8a2786e..70f07321f1825 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -874,7 +874,7 @@ func Routes() *web.Router { m.Use(apiAuth(buildAuthGroup())) m.Use(verifyAuthWithOptions(&common.VerifyOptions{ - SignInRequired: setting.Service.RequireSignInView, + SignInRequired: setting.Service.RequireSignInViewStrict, })) addActionsRoutes := func( diff --git a/routers/common/blockexpensive.go b/routers/common/blockexpensive.go new file mode 100644 index 0000000000000..f52aa2b709286 --- /dev/null +++ b/routers/common/blockexpensive.go @@ -0,0 +1,90 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "net/http" + "strings" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/reqctx" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web/middleware" + + "github.com/go-chi/chi/v5" +) + +func BlockExpensive() func(next http.Handler) http.Handler { + if !setting.Service.BlockAnonymousAccessExpensive { + return nil + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ret := determineRequestPriority(reqctx.FromContext(req.Context())) + if !ret.SignedIn { + if ret.Expensive || ret.LongPolling { + http.Redirect(w, req, setting.AppSubURL+"/user/login", http.StatusSeeOther) + return + } + } + next.ServeHTTP(w, req) + }) + } +} + +func isRoutePathExpensive(routePattern string) bool { + if strings.HasPrefix(routePattern, "/user/") || strings.HasPrefix(routePattern, "/login/") { + return false + } + + expensivePaths := []string{ + // code related + "/{username}/{reponame}/archive/", + "/{username}/{reponame}/blame/", + "/{username}/{reponame}/commit/", + "/{username}/{reponame}/commits/", + "/{username}/{reponame}/graph", + "/{username}/{reponame}/media/", + "/{username}/{reponame}/raw/", + "/{username}/{reponame}/src/", + + // issue & PR related (no trailing slash) + "/{username}/{reponame}/issues", + "/{username}/{reponame}/{type:issues}", + "/{username}/{reponame}/pulls", + "/{username}/{reponame}/{type:pulls}", + + // wiki + "/{username}/{reponame}/wiki/", + + // activity + "/{username}/{reponame}/activity/", + } + for _, path := range expensivePaths { + if strings.HasPrefix(routePattern, path) { + return true + } + } + return false +} + +func isRoutePathForLongPolling(routePattern string) bool { + return routePattern == "/user/events" +} + +func determineRequestPriority(reqCtx reqctx.RequestContext) (ret struct { + SignedIn bool + Expensive bool + LongPolling bool +}, +) { + chiRoutePath := chi.RouteContext(reqCtx).RoutePattern() + if _, ok := reqCtx.GetData()[middleware.ContextDataKeySignedUser].(*user_model.User); ok { + ret.SignedIn = true + } else { + ret.Expensive = isRoutePathExpensive(chiRoutePath) + ret.LongPolling = isRoutePathForLongPolling(chiRoutePath) + } + return ret +} diff --git a/routers/common/blockexpensive_test.go b/routers/common/blockexpensive_test.go new file mode 100644 index 0000000000000..db5c0db7ddaa6 --- /dev/null +++ b/routers/common/blockexpensive_test.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBlockExpensive(t *testing.T) { + cases := []struct { + expensive bool + routePath string + }{ + {false, "/user/xxx"}, + {false, "/login/xxx"}, + {true, "/{username}/{reponame}/archive/xxx"}, + {true, "/{username}/{reponame}/graph"}, + {true, "/{username}/{reponame}/src/xxx"}, + {true, "/{username}/{reponame}/wiki/xxx"}, + {true, "/{username}/{reponame}/activity/xxx"}, + } + for _, c := range cases { + assert.Equal(t, c.expensive, isRoutePathExpensive(c.routePath), "routePath: %s", c.routePath) + } + + assert.True(t, isRoutePathForLongPolling("/user/events")) +} diff --git a/routers/install/install.go b/routers/install/install.go index e420d36da5a1b..73ce97a334551 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -156,7 +156,7 @@ func Install(ctx *context.Context) { form.DisableRegistration = setting.Service.DisableRegistration form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration form.EnableCaptcha = setting.Service.EnableCaptcha - form.RequireSignInView = setting.Service.RequireSignInView + form.RequireSignInView = setting.Service.RequireSignInViewStrict form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking diff --git a/routers/private/serv.go b/routers/private/serv.go index 4dd7d06fb36e2..12ea01a7e6089 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -286,7 +286,7 @@ func ServCommand(ctx *context.PrivateContext) { repo.IsPrivate || owner.Visibility.IsPrivate() || (user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey - setting.Service.RequireSignInView) { + setting.Service.RequireSignInViewStrict) { if key.Type == asymkey_model.KeyTypeDeploy { if deployKey.Mode < mode { ctx.JSON(http.StatusUnauthorized, private.Response{ diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 58a2bdbab1c34..2c2f59b7be698 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -127,7 +127,7 @@ func httpBase(ctx *context.Context) *serviceHandler { // Only public pull don't need auth. isPublicPull := repoExist && !repo.IsPrivate && isPull var ( - askAuth = !isPublicPull || setting.Service.RequireSignInView + askAuth = !isPublicPull || setting.Service.RequireSignInViewStrict environ []string ) diff --git a/routers/web/web.go b/routers/web/web.go index ae5f51d40385c..9f3fb7aa5f1b4 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -285,23 +285,23 @@ func Routes() *web.Router { mid = append(mid, repo.GetActiveStopwatch) mid = append(mid, goGet) - others := web.NewRouter() - others.Use(mid...) - registerRoutes(others) - routes.Mount("", others) + webRoutes := web.NewRouter() + webRoutes.Use(mid...) + webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive()) + routes.Mount("", webRoutes) return routes } var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true}) -// registerRoutes register routes -func registerRoutes(m *web.Router) { +// registerWebRoutes register routes +func registerWebRoutes(m *web.Router) { // required to be signed in or signed out reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true}) // optional sign in (if signed in, use the user as doer, if not, no doer) - optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) - optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) + optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict}) + optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView}) validation.AddBindingRules() diff --git a/services/context/package.go b/services/context/package.go index 271b61e99c323..33855b1101292 100644 --- a/services/context/package.go +++ b/services/context/package.go @@ -93,7 +93,7 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, any)) } func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) { - if setting.Service.RequireSignInView && (doer == nil || doer.IsGhost()) { + if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) { return perm.AccessModeNone, nil } diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index e8a8313625dcb..ae4b96702922d 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -248,7 +248,7 @@ func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository, "Initialize Cargo Config", func(t *files_service.TemporaryUploadRepository) error { var b bytes.Buffer - err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInView || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate)) + err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInViewStrict || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate)) if err != nil { return err } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 29a5e1b473d3b..88dadeb3ee5db 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -148,7 +148,7 @@
{{ctx.Locale.Tr "admin.config.enable_openid_signin"}}
{{svg (Iif .Service.EnableOpenIDSignIn "octicon-check" "octicon-x")}}
{{ctx.Locale.Tr "admin.config.require_sign_in_view"}}
-
{{svg (Iif .Service.RequireSignInView "octicon-check" "octicon-x")}}
+
{{svg (Iif .Service.RequireSignInViewStrict "octicon-check" "octicon-x")}}
{{ctx.Locale.Tr "admin.config.mail_notify"}}
{{svg (Iif .Service.EnableNotifyMail "octicon-check" "octicon-x")}}
{{ctx.Locale.Tr "admin.config.enable_captcha"}}
diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index 3905ad1b70368..cc9bf11f13033 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -111,7 +111,7 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(anonymousToken) MakeRequest(t, req, http.StatusOK) - defer test.MockVariableValue(&setting.Service.RequireSignInView, true)() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) MakeRequest(t, req, http.StatusUnauthorized) diff --git a/tests/integration/api_packages_generic_test.go b/tests/integration/api_packages_generic_test.go index baa8dd66c8613..5f410fc47047c 100644 --- a/tests/integration/api_packages_generic_test.go +++ b/tests/integration/api_packages_generic_test.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -131,11 +132,7 @@ func TestPackageGeneric(t *testing.T) { t.Run("RequireSignInView", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - - setting.Service.RequireSignInView = true - defer func() { - setting.Service.RequireSignInView = false - }() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() req = NewRequest(t, "GET", url+"/dummy.bin") MakeRequest(t, req, http.StatusUnauthorized) diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go index 132171cde4e56..6f3f26df75c21 100644 --- a/tests/integration/git_smart_http_test.go +++ b/tests/integration/git_smart_http_test.go @@ -74,7 +74,7 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) { } func testRenamedRepoRedirect(t *testing.T) { - defer test.MockVariableValue(&setting.Service.RequireSignInView, true)() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() // git client requires to get a 301 redirect response before 401 unauthorized response req := NewRequest(t, "GET", "/user2/oldrepo1/info/refs") diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index 25b66bd28b577..124f57d230fdc 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" @@ -158,3 +159,32 @@ func TestEnablePasswordSignInFormAndEnablePasskeyAuth(t *testing.T) { NewHTMLParser(t, resp.Body).AssertElement(t, ".signin-passkey", true) }) } + +func TestRequireSignInView(t *testing.T) { + defer tests.PrepareTestEnv(t)() + t.Run("NoRequireSignInView", func(t *testing.T) { + require.False(t, setting.Service.RequireSignInViewStrict) + require.False(t, setting.Service.BlockAnonymousAccessExpensive) + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") + MakeRequest(t, req, http.StatusOK) + }) + t.Run("RequireSignInView", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) + }) + t.Run("BlockAnonymousAccessExpensive", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, false)() + defer test.MockVariableValue(&setting.Service.BlockAnonymousAccessExpensive, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() + + req := NewRequest(t, "GET", "/user2/repo1") + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user2/repo1/src/branch/master") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) + }) +} From 4a38adfcc6e9ab52baf63d6a6685aa80009e942a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 30 Mar 2025 13:34:30 +0800 Subject: [PATCH 2/3] fix backport --- routers/api/v1/api.go | 2 +- routers/common/blockexpensive.go | 11 ++++++----- tests/integration/api_org_test.go | 4 ++-- tests/integration/signin_test.go | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 70f07321f1825..f937a475b361a 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -356,7 +356,7 @@ func reqToken() func(ctx *context.APIContext) { func reqExploreSignIn() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { - if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned { + if (setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned { ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users") } } diff --git a/routers/common/blockexpensive.go b/routers/common/blockexpensive.go index f52aa2b709286..1aa0f48d2f013 100644 --- a/routers/common/blockexpensive.go +++ b/routers/common/blockexpensive.go @@ -4,11 +4,11 @@ package common import ( + "context" "net/http" "strings" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" @@ -21,7 +21,7 @@ func BlockExpensive() func(next http.Handler) http.Handler { } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ret := determineRequestPriority(reqctx.FromContext(req.Context())) + ret := determineRequestPriority(req.Context()) if !ret.SignedIn { if ret.Expensive || ret.LongPolling { http.Redirect(w, req, setting.AppSubURL+"/user/login", http.StatusSeeOther) @@ -73,14 +73,15 @@ func isRoutePathForLongPolling(routePattern string) bool { return routePattern == "/user/events" } -func determineRequestPriority(reqCtx reqctx.RequestContext) (ret struct { +func determineRequestPriority(ctx context.Context) (ret struct { SignedIn bool Expensive bool LongPolling bool }, ) { - chiRoutePath := chi.RouteContext(reqCtx).RoutePattern() - if _, ok := reqCtx.GetData()[middleware.ContextDataKeySignedUser].(*user_model.User); ok { + dataStore := middleware.GetContextData(ctx) + chiRoutePath := chi.RouteContext(ctx).RoutePattern() + if _, ok := dataStore[middleware.ContextDataKeySignedUser].(*user_model.User); ok { ret.SignedIn = true } else { ret.Expensive = isRoutePathExpensive(chiRoutePath) diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index fff121490c9ca..9c1082c48e3ea 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -148,9 +148,9 @@ func TestAPIOrgEditBadVisibility(t *testing.T) { func TestAPIOrgDeny(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { - setting.Service.RequireSignInView = true + setting.Service.RequireSignInViewStrict = true defer func() { - setting.Service.RequireSignInView = false + setting.Service.RequireSignInViewStrict = false }() orgName := "user1_org" diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index 124f57d230fdc..c0932f444eed9 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -21,6 +21,7 @@ import ( "github.com/markbates/goth" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func testLoginFailed(t *testing.T, username, password, message string) { From 55d25b8f419e9401b6c6f8ea44e3aef51732db8d Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 30 Mar 2025 13:39:16 +0800 Subject: [PATCH 3/3] don't use Fatal to avoid breaking --- custom/conf/app.example.ini | 2 +- modules/setting/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 2f3a5acfefbc5..cb85082839957 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -774,7 +774,7 @@ LEVEL = Info ;ALLOW_ONLY_EXTERNAL_REGISTRATION = false ;; ;; User must sign in to view anything. -;; It could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources, +;; After 1.23.7, it could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources, ;; for example: block anonymous AI crawlers from accessing repo code pages. ;; The "expensive" mode is experimental and subject to change. ;REQUIRE_SIGNIN_VIEW = false diff --git a/modules/setting/service.go b/modules/setting/service.go index d9535efec6ee8..6f0bcb48bbbfa 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -168,7 +168,7 @@ func loadServiceFrom(rootCfg ConfigProvider) { // non-boolean value only supports "expensive" at the moment Service.BlockAnonymousAccessExpensive = s == "expensive" if !Service.BlockAnonymousAccessExpensive { - log.Fatal("Invalid config option: REQUIRE_SIGNIN_VIEW = %s", s) + log.Error("Invalid config option: REQUIRE_SIGNIN_VIEW = %s", s) } }