21025: Handle api_token as a list throughout keep-web handler
[arvados.git] / services / keep-web / handler.go
index 3af326a1ad451483da29bb6398624b508e0d1fe2..0df19d443daceab53c599282640ee2c1233857dc 100644 (file)
@@ -293,12 +293,16 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                reqTokens = auth.CredentialsFromRequest(r).Tokens
        }
 
-       formToken := r.FormValue("api_token")
+       r.ParseForm()
        origin := r.Header.Get("Origin")
        cors := origin != "" && !strings.HasSuffix(origin, "://"+r.Host)
        safeAjax := cors && (r.Method == http.MethodGet || r.Method == http.MethodHead)
-       safeAttachment := attachment && r.URL.Query().Get("api_token") == ""
-       if formToken == "" {
+       // Important distiction: safeAttachment checks whether api_token exists as
+       // a query parameter. The following condition checks whether api_token
+       // exists as request form data *or* a query parameter. This distinction is
+       // necessary to redirect when required, and not when not.
+       safeAttachment := attachment && !r.URL.Query().Has("api_token")
+       if formTokens, haveFormTokens := r.Form["api_token"]; !haveFormTokens {
                // No token to use or redact.
        } else if safeAjax || safeAttachment {
                // If this is a cross-origin request, the URL won't
@@ -313,7 +317,9 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                // form?" problem, so provided the token isn't
                // embedded in the URL, there's no reason to do
                // redirect-with-cookie in this case either.
-               reqTokens = append(reqTokens, formToken)
+               for _, tok := range formTokens {
+                       reqTokens = append(reqTokens, tok)
+               }
        } else if browserMethod[r.Method] {
                // If this is a page view, and the client provided a
                // token via query string or POST body, we must put
@@ -776,7 +782,7 @@ func applyContentDispositionHdr(w http.ResponseWriter, r *http.Request, filename
 }
 
 func (h *handler) seeOtherWithCookie(w http.ResponseWriter, r *http.Request, location string, credentialsOK bool) {
-       if formToken := r.FormValue("api_token"); formToken != "" {
+       if formTokens, haveFormTokens := r.Form["api_token"]; haveFormTokens {
                if !credentialsOK {
                        // It is not safe to copy the provided token
                        // into a cookie unless the current vhost
@@ -797,13 +803,19 @@ func (h *handler) seeOtherWithCookie(w http.ResponseWriter, r *http.Request, loc
                // bar, and in the case of a POST request to avoid
                // raising warnings when the user refreshes the
                // resulting page.
-               http.SetCookie(w, &http.Cookie{
-                       Name:     "arvados_api_token",
-                       Value:    auth.EncodeTokenCookie([]byte(formToken)),
-                       Path:     "/",
-                       HttpOnly: true,
-                       SameSite: http.SameSiteLaxMode,
-               })
+               for _, tok := range formTokens {
+                       if tok == "" {
+                               continue
+                       }
+                       http.SetCookie(w, &http.Cookie{
+                               Name:     "arvados_api_token",
+                               Value:    auth.EncodeTokenCookie([]byte(tok)),
+                               Path:     "/",
+                               HttpOnly: true,
+                               SameSite: http.SameSiteLaxMode,
+                       })
+                       break
+               }
        }
 
        // Propagate query parameters (except api_token) from