diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 26c512b6f6..c714ed9660 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -461,6 +461,11 @@ INTERNAL_TOKEN = ;; Name of cookie used to store authentication information. ;COOKIE_REMEMBER_NAME = gitea_incredible ;; +;; URL or path that Gitea should redirect users to *after* performing its own logout. +;; Use this, if needed, when authentication is handled by a reverse proxy or SSO. +;; For example: "/my-sso/logout?return=/my-sso/home" +;REVERSE_PROXY_LOGOUT_REDIRECT = +;; ;; Reverse proxy authentication header name of user name, email, and full name ;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER ;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL diff --git a/modules/setting/security.go b/modules/setting/security.go index a1fd0bce2e..152bcffd9f 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -31,6 +31,7 @@ var ( ReverseProxyAuthEmail string ReverseProxyAuthFullName string ReverseProxyLimit int + ReverseProxyLogoutRedirect string ReverseProxyTrustedProxies []string MinPasswordLength int ImportLocalPaths bool @@ -124,6 +125,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) { ReverseProxyAuthFullName = sec.Key("REVERSE_PROXY_AUTHENTICATION_FULL_NAME").MustString("X-WEBAUTH-FULLNAME") ReverseProxyLimit = sec.Key("REVERSE_PROXY_LIMIT").MustInt(1) + ReverseProxyLogoutRedirect = sec.Key("REVERSE_PROXY_LOGOUT_REDIRECT").String() ReverseProxyTrustedProxies = sec.Key("REVERSE_PROXY_TRUSTED_PROXIES").Strings(",") if len(ReverseProxyTrustedProxies) == 0 { ReverseProxyTrustedProxies = []string{"127.0.0.0/8", "::1/128"} diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index e5e30667f0..d705062b3d 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -493,12 +493,17 @@ func SignOut(ctx *context.Context) { } func buildSignOutRedirectURL(ctx *context.Context) string { - // TODO: can also support REVERSE_PROXY_AUTHENTICATION logout URL in the future if ctx.Doer != nil && ctx.Doer.LoginType == auth.OAuth2 { if s := buildOIDCEndSessionURL(ctx, ctx.Doer); s != "" { return s } } + + // The assumption is: if reverse proxy auth is enabled, then the users should only sign-in via reverse proxy auth. + // TODO: in the future, if we need to distinguish different sign-in methods, we need to save the sign-in method in session and check here + if setting.Service.EnableReverseProxyAuth && setting.ReverseProxyLogoutRedirect != "" { + return setting.ReverseProxyLogoutRedirect + } return setting.AppSubURL + "/" } diff --git a/tests/integration/signout_test.go b/tests/integration/signout_test.go index 0c0ac5dd87..ff35dcf7a5 100644 --- a/tests/integration/signout_test.go +++ b/tests/integration/signout_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" @@ -16,13 +17,29 @@ import ( func TestSignOut(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") + t.Run("NormalLogout", func(t *testing.T) { + session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/logout") - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/", test.RedirectURL(resp)) + req := NewRequest(t, "GET", "/user/logout") + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", resp.Header().Get("Location")) - // try to view a private repo, should fail - req = NewRequest(t, "GET", "/user2/repo2") - session.MakeRequest(t, req, http.StatusNotFound) + // logged out, try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("ReverseProxyLogoutRedirect", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.EnableReverseProxyAuth, true)() + defer test.MockVariableValue(&setting.ReverseProxyLogoutRedirect, "/my-sso/logout?return_to=/my-sso/home")() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user/logout") + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/my-sso/logout?return_to=/my-sso/home", resp.Header().Get("Location")) + + // logged out, try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) + }) }