You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lp...@apache.org on 2023/05/15 02:17:52 UTC

[shiro] branch main updated: enh(jakarta-ee): added form resubmit retry logic in case internal authentication fails

This is an automated email from the ASF dual-hosted git repository.

lprimak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shiro.git


The following commit(s) were added to refs/heads/main by this push:
     new 5dc8082d3 enh(jakarta-ee): added form resubmit retry logic in case internal authentication fails
5dc8082d3 is described below

commit 5dc8082d39d9b29b77a3dbd3d209d98f031d1fe3
Author: lprimak <le...@flowlogix.com>
AuthorDate: Sun May 14 21:17:23 2023 -0500

    enh(jakarta-ee): added form resubmit retry logic in case internal authentication fails
---
 .../shiro/ee/filters/FormResubmitSupport.java      | 102 +++++++++++++++------
 .../ee/filters/FormResubmitSupportCookies.java     |  10 +-
 .../apache/shiro/ee/filters/FormSupportTest.java   |   2 +
 3 files changed, 84 insertions(+), 30 deletions(-)

diff --git a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
index 384c84d88..9841ea82a 100644
--- a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
+++ b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
@@ -15,20 +15,24 @@ package org.apache.shiro.ee.filters;
 
 import static org.apache.shiro.ee.filters.FormAuthenticationFilter.LOGIN_URL_ATTR_NAME;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.HttpHeaderContstants.CONTENT_TYPE;
+import static org.apache.shiro.ee.filters.FormResubmitSupport.HttpHeaderContstants.COOKIE;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.HttpHeaderContstants.LOCATION;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.HttpHeaderContstants.SET_COOKIE;
+import static org.apache.shiro.ee.filters.FormResubmitSupport.HttpResponseCodes.AUTHFAIL;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.HttpResponseCodes.FOUND;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.HttpResponseCodes.OK;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.MediaType.APPLICATION_FORM_URLENCODED;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.MediaType.TEXT_XML;
 import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.DONT_ADD_ANY_MORE_COOKIES;
 import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.addCookie;
+import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.cookieStreamFromHeader;
 import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.deleteCookie;
 import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.getCookieAge;
 import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.getSessionCookieName;
-import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.transformCookieHeader;
+import java.util.Collections;
 import org.apache.shiro.ee.filters.Forms.FallbackPredicate;
 import org.apache.shiro.ee.filters.ShiroFilter.WrappedSecurityManager;
+import static org.apache.shiro.ee.filters.FormResubmitSupportCookies.transformCookieHeader;
 import static org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isFormResumbitDisabled;
 import java.io.IOException;
 import java.net.CookieManager;
@@ -103,6 +107,7 @@ public class FormResubmitSupport {
     static class HttpHeaderContstants {
         static final String CONTENT_TYPE = "Content-Type";
         static final String LOCATION = "Location";
+        static final String COOKIE = "Cookie";
         static final String SET_COOKIE = "Set-Cookie";
     }
 
@@ -114,6 +119,7 @@ public class FormResubmitSupport {
     static class HttpResponseCodes {
         static final int OK = 200;
         static final int FOUND = 302;
+        static final int AUTHFAIL = 401;
     }
 
     @RequiredArgsConstructor
@@ -353,21 +359,24 @@ public class FormResubmitSupport {
             HttpServletRequest originalRequest, HttpServletResponse originalResponse,
             ServletContext servletContext, boolean rememberedAjaxResubmit)
             throws InterruptedException, URISyntaxException, IOException {
-        log.debug("saved form data: {}", savedFormData);
-        HttpClient client = buildHttpClient(savedRequest, servletContext, originalRequest);
-        PartialAjaxResult decodedFormData = parseFormData(savedFormData, savedRequest, client, servletContext);
-        HttpRequest postRequest = HttpRequest.newBuilder().uri(URI.create(savedRequest))
-                .POST(HttpRequest.BodyPublishers.ofString(decodedFormData.result))
-                .headers(CONTENT_TYPE, APPLICATION_FORM_URLENCODED,
-                        FORM_IS_RESUBMITTED, Boolean.TRUE.toString())
-                .build();
-        HttpResponse<String> response = client.send(postRequest, HttpResponse.BodyHandlers.ofString());
-        log.debug("Resubmit request: {}, response: {}", postRequest, response);
+        if (log.isDebugEnabled()) {
+            log.debug("saved form data: {}", savedFormData);
+            log.debug("Set Cookie Headers: {}", originalResponse.getHeaders(SET_COOKIE));
+            log.debug("Original Request Headers: {}", Collections.list(originalRequest.getHeaderNames()));
+            log.debug("Original Request Cookie Header: {}", Collections.list(originalRequest.getHeaders(COOKIE)));
+        }
+        if (Boolean.TRUE.toString().equals(originalRequest.getHeader(FORM_IS_RESUBMITTED))) {
+            log.debug("Form resubmit: internal auth failure");
+            originalResponse.setStatus(AUTHFAIL);
+            return resubmitResponseCleanup(originalRequest);
+        }
+        var savedRequestURI = URI.create(savedRequest);
+        HttpClient client = buildHttpClient(savedRequestURI, servletContext, originalRequest);
+        PartialAjaxResult decodedFormData = parseFormData(savedFormData, savedRequestURI, client, servletContext);
+        HttpRequest postRequest = constructPostRequest(savedRequestURI, decodedFormData.result);
+        HttpResponse<String> response = sendResubmitRequest(client, postRequest);
         if (rememberedAjaxResubmit && !decodedFormData.isStatelessRequest) {
-            HttpRequest redirectRequest = HttpRequest.newBuilder().uri(URI.create(savedRequest))
-                    .POST(HttpRequest.BodyPublishers.ofString(savedFormData))
-                    .headers(CONTENT_TYPE, APPLICATION_FORM_URLENCODED)
-                    .build();
+            HttpRequest redirectRequest = constructPostRequest(savedRequestURI, savedFormData);
             var redirectResponse = client.send(redirectRequest, HttpResponse.BodyHandlers.ofString());
             log.debug("Redirect request: {}, response: {}", redirectRequest, redirectResponse);
             return processResubmitResponse(redirectResponse, originalRequest, originalResponse,
@@ -381,7 +390,36 @@ public class FormResubmitSupport {
         }
     }
 
-    private static PartialAjaxResult parseFormData(String savedFormData, String savedRequest,
+    private static HttpRequest constructPostRequest(URI request, String body) {
+        return HttpRequest.newBuilder().uri(request)
+                .POST(HttpRequest.BodyPublishers.ofString(body))
+                .headers(CONTENT_TYPE, APPLICATION_FORM_URLENCODED,
+                        FORM_IS_RESUBMITTED, Boolean.TRUE.toString())
+                .build();
+    }
+
+    private static HttpResponse<String>
+    sendResubmitRequest(HttpClient client, HttpRequest request) throws IOException, InterruptedException {
+        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+        if (log.isDebugEnabled()) {
+            log.debug("Resubmit request: {}, response: {}", request, response);
+            log.debug("Response Headers: {}", response.headers().map());
+        }
+        if (response.statusCode() == AUTHFAIL) {
+            log.debug("processing authfail");
+            var cookieManager = (CookieManager) client.cookieHandler().get();
+            cookieStreamFromHeader(response.headers().allValues(SET_COOKIE))
+                    .forEach(cookie -> cookieManager.getCookieStore().add(request.uri(), cookie));
+            response = client.send(request, HttpResponse.BodyHandlers.ofString());
+            if (log.isDebugEnabled()) {
+                log.debug("Resubmit request(authfail): {}, response: {}", request, response);
+                log.debug("Response Headers(authfail): {}", response.headers().map());
+            }
+        }
+        return response;
+    }
+
+    private static PartialAjaxResult parseFormData(String savedFormData, URI savedRequest,
             HttpClient client, ServletContext servletContext) throws IOException, InterruptedException {
         boolean isStateless = true;
         if (!isJSFClientStateSavingMethod(servletContext)) {
@@ -412,7 +450,7 @@ public class FormResubmitSupport {
                 // do not duplicate the session cookie(s)
                 transformCookieHeader(headers.allValues(SET_COOKIE))
                         .entrySet().stream().filter(not(entry -> entry.getKey()
-                        .startsWith(getSessionCookieName(servletContext, SecurityUtils.getSecurityManager()))))
+                                .startsWith(getSessionCookieName(servletContext, SecurityUtils.getSecurityManager()))))
                         .forEach(entry -> addCookie(originalResponse, servletContext,
                                 entry.getKey(), entry.getValue(), -1));
                 if (isPartialAjaxRequest) {
@@ -424,30 +462,38 @@ public class FormResubmitSupport {
                 } else {
                     originalResponse.getWriter().append(response.body());
                 }
-                originalRequest.setAttribute(DONT_ADD_ANY_MORE_COOKIES, Boolean.TRUE);
-                if (hasFacesContext()) {
-                    Faces.responseComplete();
-                }
-                return null;
+                return resubmitResponseCleanup(originalRequest);
             default:
                 return savedRequest;
         }
     }
 
-    private static HttpClient buildHttpClient(String savedRequest, ServletContext servletContext,
+    private static String resubmitResponseCleanup(HttpServletRequest originalRequest) {
+        originalRequest.setAttribute(DONT_ADD_ANY_MORE_COOKIES, Boolean.TRUE);
+        if (hasFacesContext()) {
+            Faces.responseComplete();
+        }
+        return null;
+    }
+
+    private static HttpClient buildHttpClient(URI savedRequest, ServletContext servletContext,
             HttpServletRequest originalRequest) throws URISyntaxException {
         CookieManager cookieManager = new CookieManager();
         var session = SecurityUtils.getSubject().getSession();
         var sessionCookieName = getSessionCookieName(servletContext, SecurityUtils.getSecurityManager());
         var sessionCookie = new HttpCookie(sessionCookieName, session.getId().toString());
         sessionCookie.setPath(servletContext.getContextPath());
-        cookieManager.getCookieStore().add(new URI(savedRequest), sessionCookie);
+        sessionCookie.setVersion(0);
+        cookieManager.getCookieStore().add(savedRequest, sessionCookie);
+        log.debug("Setting Cookie {}", sessionCookieName);
         for (Cookie origCookie : originalRequest.getCookies()) {
             if (!origCookie.getName().equals(sessionCookieName)) {
                 try {
+                    log.debug("Setting Cookie {}", origCookie.getName());
                     HttpCookie cookie = new HttpCookie(origCookie.getName(), origCookie.getValue());
                     cookie.setPath(servletContext.getContextPath());
-                    cookieManager.getCookieStore().add(new URI(savedRequest), cookie);
+                    cookie.setVersion(0);
+                    cookieManager.getCookieStore().add(savedRequest, cookie);
                 } catch (IllegalArgumentException e) {
                     log.warn("Form Resubmit: Ignoring invalid cookie [{} - {}]",
                             origCookie.getName(), origCookie.getValue(), e);
@@ -479,10 +525,10 @@ public class FormResubmitSupport {
         }
     }
 
-    private static String getJSFNewViewState(String savedRequest, HttpClient client, String savedFormData)
+    private static String getJSFNewViewState(URI savedRequest, HttpClient client, String savedFormData)
             throws IOException, InterruptedException {
-        var getRequest = HttpRequest.newBuilder().uri(URI.create(savedRequest)).GET().build();
-        HttpResponse<String> htmlResponse = client.send(getRequest, HttpResponse.BodyHandlers.ofString());
+        var getRequest = HttpRequest.newBuilder().uri(savedRequest).GET().build();
+        HttpResponse<String> htmlResponse = sendResubmitRequest(client, getRequest);
         if (htmlResponse.statusCode() == OK) {
             savedFormData = extractJSFNewViewState(htmlResponse.body(), savedFormData);
         }
diff --git a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java
index e15843bcf..06e90107c 100644
--- a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java
+++ b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java
@@ -15,10 +15,12 @@ package org.apache.shiro.ee.filters;
 
 import static org.apache.shiro.ee.cdi.ShiroScopeContext.isWebContainerSessions;
 import static org.apache.shiro.ee.filters.FormResubmitSupport.getNativeSessionManager;
+import java.net.HttpCookie;
 import java.time.Duration;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletRequest;
 import javax.servlet.http.Cookie;
@@ -78,7 +80,11 @@ public class FormResubmitSupportCookies {
     }
 
     static Map<String, String> transformCookieHeader(@NonNull List<String> cookies) {
-        return cookies.stream().map(s -> s.split("[=;]"))
-                .collect(Collectors.toMap(k -> k[0], v -> (v.length > 1) ? v[1] : ""));
+        return cookieStreamFromHeader(cookies)
+                .collect(Collectors.toMap(HttpCookie::getName, HttpCookie::getValue));
+    }
+
+    static Stream<HttpCookie> cookieStreamFromHeader(@NonNull List<String> cookies) {
+        return cookies.stream().map(HttpCookie::parse).map(list -> list.get(0));
     }
 }
diff --git a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
index 2d68b8ed5..1e4008544 100644
--- a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
+++ b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
@@ -149,6 +149,8 @@ public class FormSupportTest {
         var map = Map.of("name1", "value1", "name2", "value2", "name3", "value3");
         assertEquals(map, transformCookieHeader(List.of("name1=value1", "name2=value2; path=/my/path", "name3=value3")));
         assertEquals(Map.of("name", ""), transformCookieHeader(List.of("name=")));
+        assertEquals(Map.of("JSESSIONID", "abc"),
+                transformCookieHeader(List.of("JSESSIONID=\"abc\"; $Version=\"1\"; $Path=\"/mypath\"")));
     }
 
     private static String decode(String plain) {