From f068d1092e3f511b0d41b9bb9e2570a52589bce2 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Wed, 6 Nov 2024 14:43:13 +0000 Subject: [PATCH] fixup! fixup! Downgrade gorilla sessions preventing the publishing by traefik hub temporarily. --- helpers.go | 138 +++++++++++++++++++++++----------------------------- main.go | 10 ++++ settings.go | 5 ++ 3 files changed, 75 insertions(+), 78 deletions(-) diff --git a/helpers.go b/helpers.go index 8460f8e..8f135b7 100644 --- a/helpers.go +++ b/helpers.go @@ -27,14 +27,6 @@ func generateNonce() (string, error) { return base64.URLEncoding.EncodeToString(nonceBytes), nil } -// buildFullURL constructs a full URL from scheme, host, and path -func buildFullURL(scheme, host, path string) string { - if scheme == "" { - scheme = "http" - } - return fmt.Sprintf("%s://%s%s", scheme, host, path) -} - // exchangeTokens exchanges a code or refresh token for tokens func (t *TraefikOidc) exchangeTokens(ctx context.Context, grantType, codeOrToken, redirectURL string) (*TokenResponse, error) { data := url.Values{ @@ -97,76 +89,6 @@ func (t *TraefikOidc) getNewTokenWithRefreshToken(refreshToken string) (*TokenRe return tokenResponse, nil } -// handleLogout handles the logout process -func (t *TraefikOidc) handleLogout(w http.ResponseWriter, r *http.Request) { - session, err := t.store.Get(r, cookieName) - if err != nil { - handleError(w, fmt.Sprintf("Error getting session: %v", err), http.StatusInternalServerError, t.logger) - return - } - - // Get tokens from session - idToken, _ := session.Values["id_token"].(string) - refreshToken, _ := session.Values["refresh_token"].(string) - accessToken, _ := session.Values["access_token"].(string) - - // Revoke tokens if they exist - if refreshToken != "" { - t.RevokeTokenWithProvider(refreshToken, "refresh_token") - t.RevokeToken(refreshToken) - } - if accessToken != "" { - t.RevokeTokenWithProvider(accessToken, "access_token") - t.RevokeToken(accessToken) - } - - // Clear session - session.Options.MaxAge = -1 - session.Values = make(map[interface{}]interface{}) - if err := session.Save(r, w); err != nil { - handleError(w, fmt.Sprintf("Error saving session: %v", err), http.StatusInternalServerError, t.logger) - return - } - - // Determine redirect URL - host := r.Header.Get("X-Forwarded-Host") - if host == "" { - host = r.Host - } - scheme := "http" - if r.Header.Get("X-Forwarded-Proto") == "https" || t.forceHTTPS { - scheme = "https" - } - baseURL := fmt.Sprintf("%s://%s/", scheme, host) - - if t.endSessionURL != "" && idToken != "" { - logoutURL, err := BuildLogoutURL(t.endSessionURL, idToken, baseURL) - if err != nil { - handleError(w, fmt.Sprintf("Invalid end session URL: %v", err), http.StatusInternalServerError, t.logger) - return - } - http.Redirect(w, r, logoutURL, http.StatusFound) - return - } - - http.Redirect(w, r, baseURL, http.StatusFound) -} - -// BuildLogoutURL constructs the logout URL with proper encoding -func BuildLogoutURL(endSessionURL, idToken, postLogoutRedirectURI string) (string, error) { - u, err := url.Parse(endSessionURL) - if err != nil { - return "", fmt.Errorf("invalid end session URL: %v", err) - } - - q := u.Query() - q.Set("id_token_hint", idToken) - q.Set("post_logout_redirect_uri", postLogoutRedirectURI) - u.RawQuery = q.Encode() - - return u.String(), nil -} - // handleExpiredToken handles the case when a token has expired func (t *TraefikOidc) handleExpiredToken(rw http.ResponseWriter, req *http.Request, session *sessions.Session) { // Clear the existing session @@ -441,3 +363,63 @@ func createStringMap(keys []string) map[string]struct{} { } return result } + +// handleLogout handles the logout request +func (t *TraefikOidc) handleLogout(rw http.ResponseWriter, req *http.Request) { + session, err := t.store.Get(req, cookieName) + if err != nil { + t.logger.Errorf("Error getting session: %v", err) + http.Error(rw, "Session error", http.StatusInternalServerError) + return + } + + // Get the id_token before clearing the session + idToken, _ := session.Values["id_token"].(string) + + // Clear and expire the session + session.Values = make(map[interface{}]interface{}) + session.Options.MaxAge = -1 + if err := session.Save(req, rw); err != nil { + t.logger.Errorf("Error saving session: %v", err) + http.Error(rw, "Session error", http.StatusInternalServerError) + return + } + + // Build post logout redirect URI if not set + if t.postLogoutRedirectURI == "" { + t.postLogoutRedirectURI = buildFullURL(t.scheme, t.determineHost(req), "/") + } + + // If we have an end session endpoint and an ID token, use OIDC end session + if t.endSessionURL != "" && idToken != "" { + logoutURL, err := BuildLogoutURL(t.endSessionURL, idToken, t.logoutURLPath) + if err != nil { + handleError(rw, fmt.Sprintf("Failed to build logout URL: %v", err), http.StatusInternalServerError, t.logger) + return + } + t.logger.Debugf("Redirecting to end session URL: %s", logoutURL) + http.Redirect(rw, req, logoutURL, http.StatusFound) + return + } + + // If no end session endpoint or no ID token, just redirect to the post logout URI + t.logger.Debugf("Redirecting to post logout URI: %s", t.postLogoutRedirectURI) + http.Redirect(rw, req, t.postLogoutRedirectURI, http.StatusFound) +} + +// BuildLogoutURL constructs the OIDC end session URL +func BuildLogoutURL(endSessionURL, idToken, postLogoutRedirectURI string) (string, error) { + u, err := url.Parse(endSessionURL) + if err != nil { + return "", fmt.Errorf("failed to parse end session URL: %w", err) + } + + q := u.Query() + q.Set("id_token_hint", idToken) + if postLogoutRedirectURI != "" { + q.Set("post_logout_redirect_uri", postLogoutRedirectURI) + } + u.RawQuery = q.Encode() + + return u.String(), nil +} diff --git a/main.go b/main.go index a581dbf..1662642 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,7 @@ type TraefikOidc struct { initComplete chan struct{} endSessionURL string baseURL string + postLogoutRedirectURI string } // ProviderMetadata holds OIDC provider metadata @@ -241,6 +242,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h allowedUserDomains: createStringMap(config.AllowedUserDomains), allowedRolesAndGroups: createStringMap(config.AllowedRolesAndGroups), initComplete: make(chan struct{}), + postLogoutRedirectURI: config.PostLogoutRedirectURI, } t.extractClaimsFunc = extractClaims @@ -767,3 +769,11 @@ func (t *TraefikOidc) extractGroupsAndRoles(idToken string) ([]string, []string, return groups, roles, nil } + +// buildFullURL constructs a full URL from scheme, host and path +func buildFullURL(scheme, host, path string) string { + if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { + return path + } + return fmt.Sprintf("%s://%s%s", scheme, host, path) +} diff --git a/settings.go b/settings.go index 053170b..63c3faa 100644 --- a/settings.go +++ b/settings.go @@ -31,6 +31,7 @@ type Config struct { AllowedUserDomains []string `json:"allowedUserDomains"` AllowedRolesAndGroups []string `json:"allowedRolesAndGroups"` OIDCEndSessionURL string `json:"oidcEndSessionURL"` + PostLogoutRedirectURI string `json:"postLogoutRedirectURI"` HTTPClient *http.Client } @@ -62,6 +63,10 @@ func CreateConfig() *Config { c.RateLimit = 100 } + if c.PostLogoutRedirectURI == "" { + c.PostLogoutRedirectURI = c.CallbackURL + } + return c }