mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 12:37:14 -04:00 
			
		
		
		
	Backport #34024 since there are too many AI crawlers. The new code is covered by tests and it does nothing if users don't set it.
This commit is contained in:
		| @@ -774,6 +774,9 @@ LEVEL = Info | ||||
| ;ALLOW_ONLY_EXTERNAL_REGISTRATION = false | ||||
| ;; | ||||
| ;; User must sign in to view anything. | ||||
| ;; 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 | ||||
| ;; | ||||
| ;; Mail notification | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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.Error("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) | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 | ||||
| 		} | ||||
|   | ||||
| @@ -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") | ||||
| 		} | ||||
| 	} | ||||
| @@ -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( | ||||
|   | ||||
							
								
								
									
										91
									
								
								routers/common/blockexpensive.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								routers/common/blockexpensive.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| // Copyright 2025 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package common | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"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(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(ctx context.Context) (ret struct { | ||||
| 	SignedIn    bool | ||||
| 	Expensive   bool | ||||
| 	LongPolling bool | ||||
| }, | ||||
| ) { | ||||
| 	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) | ||||
| 		ret.LongPolling = isRoutePathForLongPolling(chiRoutePath) | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
							
								
								
									
										30
									
								
								routers/common/blockexpensive_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								routers/common/blockexpensive_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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")) | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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{ | ||||
|   | ||||
| @@ -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 | ||||
| 	) | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| 			} | ||||
|   | ||||
| @@ -148,7 +148,7 @@ | ||||
| 				<dt>{{ctx.Locale.Tr "admin.config.enable_openid_signin"}}</dt> | ||||
| 				<dd>{{svg (Iif .Service.EnableOpenIDSignIn "octicon-check" "octicon-x")}}</dd> | ||||
| 				<dt>{{ctx.Locale.Tr "admin.config.require_sign_in_view"}}</dt> | ||||
| 				<dd>{{svg (Iif .Service.RequireSignInView "octicon-check" "octicon-x")}}</dd> | ||||
| 				<dd>{{svg (Iif .Service.RequireSignInViewStrict "octicon-check" "octicon-x")}}</dd> | ||||
| 				<dt>{{ctx.Locale.Tr "admin.config.mail_notify"}}</dt> | ||||
| 				<dd>{{svg (Iif .Service.EnableNotifyMail "octicon-check" "octicon-x")}}</dd> | ||||
| 				<dt>{{ctx.Locale.Tr "admin.config.enable_captcha"}}</dt> | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -15,11 +15,13 @@ 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" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func testLoginFailed(t *testing.T, username, password, message string) { | ||||
| @@ -158,3 +160,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")) | ||||
| 	}) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user