github.com/argoproj/argo-cd/v2@v2.10.9/server/logout/logout.go (about)

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