diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 2ade8455909a9..d733e8a67c71b 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -463,6 +463,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. +;; Mellon example: REVERSE_PROXY_LOGOUT_REDIRECT = /mellon/logout?ReturnTo=/ +;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 153b6bc944ff5..d53097aaef79f 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -25,6 +25,7 @@ var ( ReverseProxyAuthEmail string ReverseProxyAuthFullName string ReverseProxyLimit int + ReverseProxyLogoutRedirect string ReverseProxyTrustedProxies []string MinPasswordLength int ImportLocalPaths bool @@ -121,6 +122,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").MustString("") 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 2ccd1c71b5ce3..04d1e951af496 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -416,7 +416,11 @@ func SignOut(ctx *context.Context) { }) } HandleSignOut(ctx) - ctx.JSONRedirect(setting.AppSubURL + "/") + if setting.ReverseProxyLogoutRedirect != "" { + ctx.Redirect(setting.ReverseProxyLogoutRedirect) + return + } + ctx.Redirect(setting.AppSubURL + "/") } // SignUp render the register page diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 8cb72d6f98e69..57af27c4fd4e0 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -55,7 +55,8 @@
- + + {{svg "octicon-sign-out"}} {{ctx.Locale.Tr "sign_out"}} @@ -128,7 +129,8 @@ {{end}} - + + {{svg "octicon-sign-out"}} {{ctx.Locale.Tr "sign_out"}} diff --git a/tests/integration/signout_test.go b/tests/integration/signout_test.go index 7fd0b5c64a017..5ff42b8af788f 100644 --- a/tests/integration/signout_test.go +++ b/tests/integration/signout_test.go @@ -7,6 +7,8 @@ import ( "net/http" "testing" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" ) @@ -16,7 +18,34 @@ func TestSignOut(t *testing.T) { session := loginUser(t, "user2") req := NewRequest(t, "POST", "/user/logout") - session.MakeRequest(t, req, http.StatusOK) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + + expected := "/" + loc := resp.Header().Get("Location") + if loc != expected { + t.Fatalf("expected redirect to %q, got %q", expected, loc) + } + + // try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) +} + +func TestSignOut_ReverseProxyLogoutRedirect(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + defer test.MockVariableValue(&setting.ReverseProxyLogoutRedirect, "/mellon/logout?ReturnTo=/")() + + session := loginUser(t, "user2") + + req := NewRequest(t, "POST", "/user/logout") + resp := session.MakeRequest(t, req, http.StatusSeeOther) + + expected := "/mellon/logout?ReturnTo=/" + loc := resp.Header().Get("Location") + if loc != expected { + t.Fatalf("expected redirect to %q, got %q", expected, loc) + } // try to view a private repo, should fail req = NewRequest(t, "GET", "/user2/repo2")