Skip to content

Commit 16d7817

Browse files
elirocawxiaoguang
andauthored
Implement logout redirection for reverse proxy auth setups (#36085)
When authentication is handled externally by a reverse proxy SSO provider, users can be redirected to an external logout URL or relative path defined on the reverse proxy. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent 681c407 commit 16d7817

4 files changed

Lines changed: 37 additions & 8 deletions

File tree

custom/conf/app.example.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,11 @@ INTERNAL_TOKEN =
461461
;; Name of cookie used to store authentication information.
462462
;COOKIE_REMEMBER_NAME = gitea_incredible
463463
;;
464+
;; URL or path that Gitea should redirect users to *after* performing its own logout.
465+
;; Use this, if needed, when authentication is handled by a reverse proxy or SSO.
466+
;; For example: "/my-sso/logout?return=/my-sso/home"
467+
;REVERSE_PROXY_LOGOUT_REDIRECT =
468+
;;
464469
;; Reverse proxy authentication header name of user name, email, and full name
465470
;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
466471
;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL

modules/setting/security.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var (
3131
ReverseProxyAuthEmail string
3232
ReverseProxyAuthFullName string
3333
ReverseProxyLimit int
34+
ReverseProxyLogoutRedirect string
3435
ReverseProxyTrustedProxies []string
3536
MinPasswordLength int
3637
ImportLocalPaths bool
@@ -124,6 +125,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
124125
ReverseProxyAuthFullName = sec.Key("REVERSE_PROXY_AUTHENTICATION_FULL_NAME").MustString("X-WEBAUTH-FULLNAME")
125126

126127
ReverseProxyLimit = sec.Key("REVERSE_PROXY_LIMIT").MustInt(1)
128+
ReverseProxyLogoutRedirect = sec.Key("REVERSE_PROXY_LOGOUT_REDIRECT").String()
127129
ReverseProxyTrustedProxies = sec.Key("REVERSE_PROXY_TRUSTED_PROXIES").Strings(",")
128130
if len(ReverseProxyTrustedProxies) == 0 {
129131
ReverseProxyTrustedProxies = []string{"127.0.0.0/8", "::1/128"}

routers/web/auth/auth.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,12 +493,17 @@ func SignOut(ctx *context.Context) {
493493
}
494494

495495
func buildSignOutRedirectURL(ctx *context.Context) string {
496-
// TODO: can also support REVERSE_PROXY_AUTHENTICATION logout URL in the future
497496
if ctx.Doer != nil && ctx.Doer.LoginType == auth.OAuth2 {
498497
if s := buildOIDCEndSessionURL(ctx, ctx.Doer); s != "" {
499498
return s
500499
}
501500
}
501+
502+
// The assumption is: if reverse proxy auth is enabled, then the users should only sign-in via reverse proxy auth.
503+
// 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
504+
if setting.Service.EnableReverseProxyAuth && setting.ReverseProxyLogoutRedirect != "" {
505+
return setting.ReverseProxyLogoutRedirect
506+
}
502507
return setting.AppSubURL + "/"
503508
}
504509

tests/integration/signout_test.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"testing"
99

10+
"code.gitea.io/gitea/modules/setting"
1011
"code.gitea.io/gitea/modules/test"
1112
"code.gitea.io/gitea/tests"
1213

@@ -16,13 +17,29 @@ import (
1617
func TestSignOut(t *testing.T) {
1718
defer tests.PrepareTestEnv(t)()
1819

19-
session := loginUser(t, "user2")
20+
t.Run("NormalLogout", func(t *testing.T) {
21+
session := loginUser(t, "user2")
2022

21-
req := NewRequest(t, "GET", "/user/logout")
22-
resp := session.MakeRequest(t, req, http.StatusSeeOther)
23-
assert.Equal(t, "/", test.RedirectURL(resp))
23+
req := NewRequest(t, "GET", "/user/logout")
24+
resp := session.MakeRequest(t, req, http.StatusSeeOther)
25+
assert.Equal(t, "/", resp.Header().Get("Location"))
2426

25-
// try to view a private repo, should fail
26-
req = NewRequest(t, "GET", "/user2/repo2")
27-
session.MakeRequest(t, req, http.StatusNotFound)
27+
// logged out, try to view a private repo, should fail
28+
req = NewRequest(t, "GET", "/user2/repo2")
29+
session.MakeRequest(t, req, http.StatusNotFound)
30+
})
31+
32+
t.Run("ReverseProxyLogoutRedirect", func(t *testing.T) {
33+
defer test.MockVariableValue(&setting.Service.EnableReverseProxyAuth, true)()
34+
defer test.MockVariableValue(&setting.ReverseProxyLogoutRedirect, "/my-sso/logout?return_to=/my-sso/home")()
35+
36+
session := loginUser(t, "user2")
37+
req := NewRequest(t, "GET", "/user/logout")
38+
resp := session.MakeRequest(t, req, http.StatusSeeOther)
39+
assert.Equal(t, "/my-sso/logout?return_to=/my-sso/home", resp.Header().Get("Location"))
40+
41+
// logged out, try to view a private repo, should fail
42+
req = NewRequest(t, "GET", "/user2/repo2")
43+
session.MakeRequest(t, req, http.StatusNotFound)
44+
})
2845
}

0 commit comments

Comments
 (0)