feat: add local logout endpoint

This commit is contained in:
Trong Huu Nguyen
2022-10-26 10:57:25 +02:00
parent 30f155a644
commit e7244df4d5
7 changed files with 72 additions and 23 deletions

View File

@@ -25,7 +25,11 @@ type Source interface {
GetSessions() *session.Handler
}
func Handler(src Source, w http.ResponseWriter, r *http.Request) {
type Options struct {
GlobalLogout bool
}
func Handler(src Source, w http.ResponseWriter, r *http.Request, opts Options) {
logger := logentry.LogEntryFrom(r)
logout, err := src.GetClient().Logout(r)
if err != nil {
@@ -49,6 +53,7 @@ func Handler(src Source, w http.ResponseWriter, r *http.Request) {
"jti": sessionData.IDTokenJwtID,
}
logger.WithFields(fields).Info("logout: successful local logout")
metrics.ObserveLogout(metrics.LogoutOperationLocal)
}
cookie.Clear(w, cookie.Session, src.GetCookieOptsPathAware(r))
@@ -57,7 +62,9 @@ func Handler(src Source, w http.ResponseWriter, r *http.Request) {
src.GetLoginstatus().ClearCookie(w, src.GetCookieOptions())
}
logger.Debug("logout: redirecting to identity provider")
metrics.ObserveLogout(metrics.LogoutOperationSelfInitiated)
http.Redirect(w, r, logout.SingleLogoutURL(idToken), http.StatusTemporaryRedirect)
if opts.GlobalLogout {
logger.Debug("logout: redirecting to identity provider for global/single-logout")
metrics.ObserveLogout(metrics.LogoutOperationSelfInitiated)
http.Redirect(w, r, logout.SingleLogoutURL(idToken), http.StatusTemporaryRedirect)
}
}

View File

@@ -111,7 +111,17 @@ func (s *StandardHandler) LoginCallback(w http.ResponseWriter, r *http.Request)
}
func (s *StandardHandler) Logout(w http.ResponseWriter, r *http.Request) {
apilogout.Handler(s, w, r)
opts := apilogout.Options{
GlobalLogout: true,
}
apilogout.Handler(s, w, r, opts)
}
func (s *StandardHandler) LogoutLocal(w http.ResponseWriter, r *http.Request) {
opts := apilogout.Options{
GlobalLogout: false,
}
apilogout.Handler(s, w, r, opts)
}
func (s *StandardHandler) LogoutCallback(w http.ResponseWriter, r *http.Request) {

View File

@@ -73,7 +73,7 @@ func TestHandler_Logout(t *testing.T) {
rpClient := idp.RelyingPartyClient()
login(t, rpClient, idp)
resp := localLogout(t, rpClient, idp)
resp := selfInitiatedLogout(t, rpClient, idp)
// Get endsession endpoint after local logout
endsessionURL := resp.Location
@@ -81,7 +81,7 @@ func TestHandler_Logout(t *testing.T) {
idpserverURL, err := url.Parse(idp.ProviderServer.URL)
assert.NoError(t, err)
req := idp.GetRequest(idp.RelyingPartyServer.URL + "/oauth2/logout")
req := idp.GetRequest(idp.RelyingPartyServer.URL + "/oauth2/logout/callback")
expectedLogoutCallbackURL, err := urlpkg.LogoutCallbackURL(req)
assert.NoError(t, err)
@@ -139,6 +139,17 @@ func TestHandler_FrontChannelLogout(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestHandler_LogoutLocal(t *testing.T) {
cfg := mock.Config()
idp := mock.NewIdentityProvider(cfg)
defer idp.Close()
rpClient := idp.RelyingPartyClient()
login(t, rpClient, idp)
localLogout(t, rpClient, idp)
}
func TestHandler_SessionStateRequired(t *testing.T) {
cfg := mock.Config()
idp := mock.NewIdentityProvider(cfg)
@@ -708,7 +719,7 @@ func login(t *testing.T, rpClient *http.Client, idp *mock.IdentityProvider) *htt
return callback(t, rpClient, resp)
}
func localLogout(t *testing.T, rpClient *http.Client, idp *mock.IdentityProvider) response {
func selfInitiatedLogout(t *testing.T, rpClient *http.Client, idp *mock.IdentityProvider) response {
// Request self-initiated logout
logoutURL, err := url.Parse(idp.RelyingPartyServer.URL + "/oauth2/logout")
assert.NoError(t, err)
@@ -726,7 +737,7 @@ func localLogout(t *testing.T, rpClient *http.Client, idp *mock.IdentityProvider
func logout(t *testing.T, rpClient *http.Client, idp *mock.IdentityProvider) {
// Get endsession endpoint after local logout
resp := localLogout(t, rpClient, idp)
resp := selfInitiatedLogout(t, rpClient, idp)
// Follow redirect to endsession endpoint at identity provider
resp = get(t, rpClient, resp.Location.String())
@@ -755,6 +766,21 @@ func logout(t *testing.T, rpClient *http.Client, idp *mock.IdentityProvider) {
assert.Nil(t, sessionCookie)
}
func localLogout(t *testing.T, rpClient *http.Client, idp *mock.IdentityProvider) response {
logoutURL, err := url.Parse(idp.RelyingPartyServer.URL + "/oauth2/logout/local")
assert.NoError(t, err)
resp := get(t, rpClient, logoutURL.String())
assert.Equal(t, http.StatusOK, resp.StatusCode)
cookies := rpClient.Jar.Cookies(logoutURL)
sessionCookie := getCookieFromJar(cookie.Session, cookies)
assert.Nil(t, sessionCookie)
return resp
}
func sessionInfo(t *testing.T, idp *mock.IdentityProvider, rpClient *http.Client) response {
sessionInfoURL, err := url.Parse(idp.RelyingPartyServer.URL + "/oauth2/session")
assert.NoError(t, err)