github.com/argoproj/argo-cd/v3@v3.2.1/server/logout/logout.go (about) 1 package logout 2 3 import ( 4 "context" 5 "net/http" 6 "regexp" 7 "strings" 8 "time" 9 10 "github.com/golang-jwt/jwt/v5" 11 log "github.com/sirupsen/logrus" 12 13 "github.com/argoproj/argo-cd/v3/common" 14 httputil "github.com/argoproj/argo-cd/v3/util/http" 15 jwtutil "github.com/argoproj/argo-cd/v3/util/jwt" 16 "github.com/argoproj/argo-cd/v3/util/session" 17 "github.com/argoproj/argo-cd/v3/util/settings" 18 ) 19 20 // NewHandler creates handler serving to do api/logout endpoint 21 func NewHandler(settingsMrg *settings.SettingsManager, sessionMgr *session.SessionManager, rootPath, baseHRef string) *Handler { 22 return &Handler{ 23 settingsMgr: settingsMrg, 24 rootPath: rootPath, 25 baseHRef: baseHRef, 26 verifyToken: sessionMgr.VerifyToken, 27 revokeToken: sessionMgr.RevokeToken, 28 } 29 } 30 31 type Handler struct { 32 settingsMgr *settings.SettingsManager 33 rootPath string 34 verifyToken func(tokenString string) (jwt.Claims, string, error) 35 revokeToken func(ctx context.Context, id string, expiringAt time.Duration) error 36 baseHRef string 37 } 38 39 var ( 40 tokenPattern = regexp.MustCompile(`{{token}}`) 41 logoutRedirectURLPattern = regexp.MustCompile(`{{logoutRedirectURL}}`) 42 ) 43 44 func constructLogoutURL(logoutURL, token, logoutRedirectURL string) string { 45 constructedLogoutURL := tokenPattern.ReplaceAllString(logoutURL, token) 46 return logoutRedirectURLPattern.ReplaceAllString(constructedLogoutURL, logoutRedirectURL) 47 } 48 49 // ServeHTTP is the logout handler for ArgoCD and constructs OIDC logout URL and redirects to it for OIDC issued sessions, 50 // and redirects user to '/login' for argocd issued sessions 51 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 52 var tokenString string 53 var oidcConfig *settings.OIDCConfig 54 55 argoCDSettings, err := h.settingsMgr.GetSettings() 56 if err != nil { 57 w.WriteHeader(http.StatusInternalServerError) 58 http.Error(w, "Failed to retrieve argoCD settings: "+err.Error(), http.StatusInternalServerError) 59 return 60 } 61 62 argoURL, err := argoCDSettings.ArgoURLForRequest(r) 63 if err != nil { 64 log.Warnf("unable to find ArgoCD URL from config: %v", err) 65 } 66 if argoURL == "" { 67 // golang does not provide any easy way to determine scheme of current request 68 // so redirecting ot http which will auto-redirect too https if necessary 69 host := strings.TrimRight(r.Host, "/") 70 argoURL = "http://" + host + "/" + strings.TrimRight(strings.TrimLeft(h.rootPath, "/"), "/") 71 } 72 73 logoutRedirectURL := strings.TrimRight(strings.TrimLeft(argoURL, "/"), "/") 74 75 cookies := r.Cookies() 76 tokenString, err = httputil.JoinCookies(common.AuthCookieName, cookies) 77 if tokenString == "" || err != nil { 78 w.WriteHeader(http.StatusBadRequest) 79 http.Error(w, "Failed to retrieve ArgoCD auth token: "+err.Error(), http.StatusBadRequest) 80 return 81 } 82 83 for _, cookie := range cookies { 84 if !strings.HasPrefix(cookie.Name, common.AuthCookieName) { 85 continue 86 } 87 88 argocdCookie := http.Cookie{ 89 Name: cookie.Name, 90 Value: "", 91 } 92 93 argocdCookie.Path = "/" + strings.TrimRight(strings.TrimLeft(h.baseHRef, "/"), "/") 94 w.Header().Add("Set-Cookie", argocdCookie.String()) 95 } 96 97 claims, _, err := h.verifyToken(tokenString) 98 if err != nil { 99 http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther) 100 return 101 } 102 103 mapClaims, err := jwtutil.MapClaims(claims) 104 if err != nil { 105 http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther) 106 return 107 } 108 109 issuer := jwtutil.StringField(mapClaims, "iss") 110 id := jwtutil.StringField(mapClaims, "jti") 111 if exp, err := jwtutil.ExpirationTime(mapClaims); err == nil && id != "" { 112 if err := h.revokeToken(context.Background(), id, time.Until(exp)); err != nil { 113 log.Warnf("failed to invalidate token '%s': %v", id, err) 114 } 115 } 116 117 if argoCDSettings.OIDCConfig() == nil || argoCDSettings.OIDCConfig().LogoutURL == "" || issuer == session.SessionManagerClaimsIssuer { 118 http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther) 119 } else { 120 oidcConfig = argoCDSettings.OIDCConfig() 121 logoutURL := constructLogoutURL(oidcConfig.LogoutURL, tokenString, logoutRedirectURL) 122 http.Redirect(w, r, logoutURL, http.StatusSeeOther) 123 } 124 }