Files
argo-cd/server/logout/logout.go
Papapetrou Patroklos ced94022b3 fix: panic during OIDC logout with empty token (#25874)
Signed-off-by: Patroklos Papapetrou <ppapapetrou76@gmail.com>
2026-01-06 14:30:28 +00:00

128 lines
4.1 KiB
Go

package logout
import (
"context"
"net/http"
"regexp"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/common"
httputil "github.com/argoproj/argo-cd/v3/util/http"
jwtutil "github.com/argoproj/argo-cd/v3/util/jwt"
"github.com/argoproj/argo-cd/v3/util/session"
"github.com/argoproj/argo-cd/v3/util/settings"
)
// NewHandler creates handler serving to do api/logout endpoint
func NewHandler(settingsMrg *settings.SettingsManager, sessionMgr *session.SessionManager, rootPath, baseHRef string) *Handler {
return &Handler{
settingsMgr: settingsMrg,
rootPath: rootPath,
baseHRef: baseHRef,
verifyToken: sessionMgr.VerifyToken,
revokeToken: sessionMgr.RevokeToken,
}
}
type Handler struct {
settingsMgr *settings.SettingsManager
rootPath string
verifyToken func(ctx context.Context, tokenString string) (jwt.Claims, string, error)
revokeToken func(ctx context.Context, id string, expiringAt time.Duration) error
baseHRef string
}
var (
tokenPattern = regexp.MustCompile(`{{token}}`)
logoutRedirectURLPattern = regexp.MustCompile(`{{logoutRedirectURL}}`)
)
func constructLogoutURL(logoutURL, token, logoutRedirectURL string) string {
constructedLogoutURL := tokenPattern.ReplaceAllString(logoutURL, token)
return logoutRedirectURLPattern.ReplaceAllString(constructedLogoutURL, logoutRedirectURL)
}
// ServeHTTP is the logout handler for ArgoCD and constructs OIDC logout URL and redirects to it for OIDC issued sessions,
// and redirects user to '/login' for argocd issued sessions
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var tokenString string
var oidcConfig *settings.OIDCConfig
argoCDSettings, err := h.settingsMgr.GetSettings()
if err != nil {
http.Error(w, "Failed to retrieve argoCD settings: "+err.Error(), http.StatusInternalServerError)
return
}
argoURL, err := argoCDSettings.ArgoURLForRequest(r)
if err != nil {
log.Warnf("unable to find ArgoCD URL from config: %v", err)
}
if argoURL == "" {
// golang does not provide any easy way to determine scheme of current request
// so redirecting ot http which will auto-redirect too https if necessary
host := strings.TrimRight(r.Host, "/")
argoURL = "http://" + host + "/" + strings.TrimRight(strings.TrimLeft(h.rootPath, "/"), "/")
}
logoutRedirectURL := strings.TrimRight(strings.TrimLeft(argoURL, "/"), "/")
cookies := r.Cookies()
tokenString, err = httputil.JoinCookies(common.AuthCookieName, cookies)
// Build message safely: only include err when non-nil
if err != nil {
http.Error(w, "Failed to retrieve ArgoCD auth token: "+err.Error(), http.StatusBadRequest)
return
}
if tokenString == "" {
http.Error(w, "Failed to retrieve ArgoCD auth token", http.StatusBadRequest)
return
}
for _, cookie := range cookies {
if !strings.HasPrefix(cookie.Name, common.AuthCookieName) {
continue
}
argocdCookie := http.Cookie{
Name: cookie.Name,
Value: "",
}
argocdCookie.Path = "/" + strings.TrimRight(strings.TrimLeft(h.baseHRef, "/"), "/")
w.Header().Add("Set-Cookie", argocdCookie.String())
}
claims, _, err := h.verifyToken(r.Context(), tokenString)
if err != nil {
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
return
}
mapClaims, err := jwtutil.MapClaims(claims)
if err != nil {
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
return
}
issuer := jwtutil.StringField(mapClaims, "iss")
id := jwtutil.StringField(mapClaims, "jti")
if exp, err := jwtutil.ExpirationTime(mapClaims); err == nil && id != "" {
if err := h.revokeToken(context.Background(), id, time.Until(exp)); err != nil {
log.Warnf("failed to invalidate token '%s': %v", id, err)
}
}
if argoCDSettings.OIDCConfig() == nil || argoCDSettings.OIDCConfig().LogoutURL == "" || issuer == session.SessionManagerClaimsIssuer {
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
} else {
oidcConfig = argoCDSettings.OIDCConfig()
logoutURL := constructLogoutURL(oidcConfig.LogoutURL, tokenString, logoutRedirectURL)
http.Redirect(w, r, logoutURL, http.StatusSeeOther)
}
}