Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions integration-tests/jakarta-ee/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@
<param-name>org.apache.shiro.form-resubmit.secure-cookies</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>org.apache.shiro.form-resubmit.whitelist.disabled</param-name>
<param-value>true</param-value>
</context-param>

<!-- Apache Shiro Security -->
<context-param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,12 @@
import static org.apache.shiro.ee.filters.FormAuthenticationFilter.LOGIN_URL_ATTR_NAME;
import static org.apache.shiro.ee.filters.FormAuthenticationFilter.LOGIN_WAITTIME_ATTR_NAME;
import static org.apache.shiro.ee.filters.FormAuthenticationFilter.NO_PREDICATE;
import static org.apache.shiro.ee.filters.FormAuthenticationFilter.getPathWithinApplication;
import static org.apache.shiro.ee.filters.FormResubmitSupport.isPostRequest;
import static org.apache.shiro.ee.filters.FormResubmitSupport.savePostDataForResubmit;
import static org.apache.shiro.ee.filters.FormResubmitSupport.saveRequestReferer;
import static org.apache.shiro.ee.filters.LogoutFilter.LOGOUT_PREDICATE_ATTR_NAME;
import static org.apache.shiro.ee.filters.LogoutFilter.YES_PREDICATE;
import static org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isFormResubmitDisabled;
import static org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isServletNoPrincipal;
import static org.apache.shiro.web.filter.authc.NoAccessFilter.FORM_RESUBMIT_CHECK_SERVLET_PATH;
import static org.apache.shiro.web.jaxrs.SubjectPrincipalRequestFilter.SHIRO_WEB_JAXRS_DISABLE_PRINCIPAL_PARAM;

/**
Expand Down Expand Up @@ -104,10 +101,7 @@ public boolean preHandle(ServletRequest request, ServletResponse response) throw
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = methods.getSubject(request, response);
boolean isAuthenticated = subject.isAuthenticated() && subject.getPrincipal() != null;
return isAuthenticated || (useRemembered && subject.isRemembered())
|| (isPostRequest(request)
&& FORM_RESUBMIT_CHECK_SERVLET_PATH.equals(getPathWithinApplication(request,
() -> WebUtils.getPathWithinApplication(WebUtils.toHttp(request)))));
return isAuthenticated || (useRemembered && subject.isRemembered());
}

/**
Expand Down

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ protected void executeChain(ServletRequest request, ServletResponse response,
log.debug("Resubmitting Post Data: {}", postData);
var httpRequest = WebUtils.toHttp(request);
boolean rememberedAjaxResubmit = "partial/ajax".equals(httpRequest.getHeader("Faces-Request"));
Optional.ofNullable(resubmitSavedForm(postData, null,
Servlets.getRequestURLWithQueryString(httpRequest),
Optional.ofNullable(resubmitSavedForm(postData,
Servlets.getRequestURIWithQueryString(httpRequest),
WebUtils.toHttp(request), WebUtils.toHttp(response),
request.getServletContext(), rememberedAjaxResubmit, false))
.ifPresent(url -> sendRedirect(response, url));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public class EnvironmentLoaderListener extends EnvironmentLoader implements Serv
private static final String SHIRO_EE_CHAR_ENCODING_PARAM = "org.apache.shiro.ee.character-encoding";
private static final String FORM_RESUBMIT_DISABLED_PARAM = "org.apache.shiro.form-resubmit.disabled";
private static final String FORM_RESUBMIT_SECURE_COOKIES = "org.apache.shiro.form-resubmit.secure-cookies";
private static final String FORM_RESUBMIT_WHITE_LIST_DISABLED = "org.apache.shiro.form-resubmit.whitelist.disabled";
private static final String FORM_RESUBMIT_BLACK_LIST_DISABLED = "org.apache.shiro.form-resubmit.blacklist.disabled";
private static final String SHIRO_WEB_DISABLE_PRINCIPAL_PARAM = "org.apache.shiro.web.disable-principal";

Expand All @@ -66,10 +65,6 @@ public static boolean isFormResubmitSecureCookies(ServletContext ctx) {
return Boolean.TRUE.equals(ctx.getAttribute(FORM_RESUBMIT_SECURE_COOKIES));
}

public static boolean isFormResubmitWhitelistEnabled(ServletContext ctx) {
return !Boolean.TRUE.equals(ctx.getAttribute(FORM_RESUBMIT_WHITE_LIST_DISABLED));
}

public static boolean isFormResubmitBlacklistEnabled(ServletContext ctx) {
return !Boolean.TRUE.equals(ctx.getAttribute(FORM_RESUBMIT_BLACK_LIST_DISABLED));
}
Expand Down Expand Up @@ -106,9 +101,6 @@ public void contextInitialized(ServletContextEvent sce) {
} else {
sce.getServletContext().setAttribute(FORM_RESUBMIT_SECURE_COOKIES, Boolean.FALSE);
}
if (Boolean.parseBoolean(sce.getServletContext().getInitParameter(FORM_RESUBMIT_WHITE_LIST_DISABLED))) {
sce.getServletContext().setAttribute(FORM_RESUBMIT_WHITE_LIST_DISABLED, Boolean.TRUE);
}
if (Boolean.parseBoolean(sce.getServletContext().getInitParameter(FORM_RESUBMIT_BLACK_LIST_DISABLED))) {
sce.getServletContext().setAttribute(FORM_RESUBMIT_BLACK_LIST_DISABLED, Boolean.TRUE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
package org.apache.shiro.ee.filters;

import jakarta.servlet.ServletContext;
import org.apache.shiro.ee.filters.FormResubmitSupport.PartialAjaxResult;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;

Expand Down Expand Up @@ -48,12 +49,15 @@
* Resubmit forms support
*/
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("checkstyle:MethodCount")
class FormSupportTest {
private static final long BLACKLISTED_AT = 1_000L;
private static final Duration BLACKLIST_TTL = Duration.ofSeconds(60);

@Mock
private HttpServletRequest request;
@Mock
private ServletContext servletContext;

@Test
void nullReferer() {
Expand All @@ -70,7 +74,6 @@ void blankReferer() {
@Test
void plainStringReferer() {
when(request.getHeader("referer")).thenReturn("hello");
when(request.getContextPath()).thenReturn("/myapp");
assertThat(getReferer(request)).isNull();
}

Expand Down Expand Up @@ -129,34 +132,26 @@ void rootContextKeepsPathWithQuery() {
}

@Test
void normalizedPathWithinContextIsAccepted() {
void nonCanonicalPathIsRejected() {
when(request.getHeader("referer")).thenReturn("https://example.com/myapp//foo/./bar.xhtml");
when(request.getContextPath()).thenReturn("/myapp");

assertThat(getReferer(request)).isEqualTo("/myapp/foo/bar.xhtml");
assertThat(getReferer(request)).isNull();
}

@Test
void normalizedPathEscapingContextIsRejected() {
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/../otherapp/page.xhtml");
when(request.getContextPath()).thenReturn("/myapp");

assertThat(getReferer(request)).isNull();
}

@Test
void opaqueUriRefererIsRejected() {
when(request.getHeader("referer")).thenReturn("mailto:test@example.com");
when(request.getContextPath()).thenReturn("/myapp");

assertThat(getReferer(request)).isNull();
}

@Test
void javascriptUriRefererIsRejected() {
when(request.getHeader("referer")).thenReturn("javascript:alert(1)");
when(request.getContextPath()).thenReturn("/myapp");

assertThat(getReferer(request)).isNull();
}

Expand All @@ -177,7 +172,7 @@ void refererWithFragmentDropsFragmentAndKeepsQueryOnly() {
}

@Test
void externalHostWithMatchingContextCurrentlyPasses() {
void externalHostIsStrippedToPath() {
when(request.getHeader("referer")).thenReturn("https://attacker.example/myapp/login.xhtml");
when(request.getContextPath()).thenReturn("/myapp");

Expand All @@ -186,19 +181,25 @@ void externalHostWithMatchingContextCurrentlyPasses() {

@Test
void encodedPathTraversalRefererIsRejected() {
when(request.getHeader("referer"))
.thenReturn("https://example.com/myapp/%2e%2e/otherapp/page.xhtml");
when(request.getContextPath()).thenReturn("/myapp");

when(request.getHeader("referer")).thenReturn("https://example.com/myapp/%2e%2e/otherapp/page.xhtml");
assertThat(getReferer(request)).isNull();
}

@Test
void encodedPathTraversalWithEncodedSlashesRefererIsRejected() {
when(request.getHeader("referer"))
.thenReturn("https://example.com/myapp/%2e%2e%2fotherapp%2fpage.xhtml");
when(request.getContextPath()).thenReturn("/myapp");
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/%2e%2e%2fotherapp%2fpage.xhtml");
assertThat(getReferer(request)).isNull();
}

@Test
void doubleSlashPathWithRootContextIsRejected() {
when(request.getHeader("referer")).thenReturn("https://example.com//evil.com/x");
assertThat(getReferer(request)).isNull();
}

@Test
void doubleSlashPathWithinContextIsRejected() {
when(request.getHeader("referer")).thenReturn("https://attacker.example//myapp/x");
assertThat(getReferer(request)).isNull();
}

Expand Down Expand Up @@ -348,18 +349,15 @@ void parseCookies() {

@Test
@SuppressWarnings("checkstyle:MagicNumber")
void whitelistAndBlacklistUseShiroCacheManager() {
void blacklistUseShiroCacheManager() {
var securityManager = new DefaultSecurityManager();
securityManager.setCacheManager(new MemoryConstrainedCacheManager());

var whitelist = FormResubmitSupport.getWhitelistCache(securityManager);
var blacklist = FormResubmitSupport.getBlacklistCache(securityManager);

whitelist.put("good.example", Boolean.TRUE);
blacklist.put("bad.example", BLACKLISTED_AT);

assertThat(FormResubmitSupport.getWhitelistCache(securityManager).get("good.example")).isTrue();
assertThat(FormResubmitSupport.isBlacklisted(blacklist, "bad.example",
assertThat(FormResubmitSupport.isBlacklisted(blacklist, null, "bad.example",
BLACKLIST_TTL, 1_500L)).isTrue();
}

Expand All @@ -372,11 +370,28 @@ void expiredBlacklistEntryIsRemovedFromShiroCache() {
var blacklist = FormResubmitSupport.getBlacklistCache(securityManager);
blacklist.put("expired.example", BLACKLISTED_AT);

assertThat(FormResubmitSupport.isBlacklisted(blacklist, "expired.example",
assertThat(FormResubmitSupport.isBlacklisted(blacklist, null, "expired.example",
BLACKLIST_TTL, 61_001L)).isFalse();
assertThat(blacklist.get("expired.example")).isNull();
}

@Test
@SuppressWarnings("checkstyle:MagicNumber")
void blacklistHonoursEnabledFlag() {
var securityManager = new DefaultSecurityManager();
securityManager.setCacheManager(new MemoryConstrainedCacheManager());
var blacklist = FormResubmitSupport.getBlacklistCache(securityManager);
blacklist.put("bad.example", BLACKLISTED_AT);

// attribute absent → enabled
assertThat(FormResubmitSupport.isBlacklisted(blacklist, servletContext, "bad.example",
BLACKLIST_TTL, 1_500L)).isTrue();

when(servletContext.getAttribute("org.apache.shiro.form-resubmit.blacklist.disabled")).thenReturn(Boolean.TRUE);
assertThat(FormResubmitSupport.isBlacklisted(blacklist, servletContext, "bad.example",
BLACKLIST_TTL, 1_500L)).isFalse();
}

private static String decode(String plain) {
return URLDecoder.decode(plain, StandardCharsets.UTF_8);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
package org.apache.shiro.web.filter.authc;

import jakarta.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
Expand All @@ -32,8 +31,6 @@
* that do not match existing filter patterns.
*/
public class NoAccessFilter extends AuthenticatingFilter {
public static final String FORM_RESUBMIT_CHECK_SERVLET_PATH = "/org.apache.shiro.form-resubmit-check";

private final Logger log = LoggerFactory.getLogger(NoAccessFilter.class);

@Override
Expand All @@ -47,14 +44,4 @@ protected boolean onAccessDenied(ServletRequest request, ServletResponse respons
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
return null;
}

@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
return httpRequest.getMethod().equals("POST")
&& httpRequest.getServletPath().equals(FORM_RESUBMIT_CHECK_SERVLET_PATH);
}
return false;
}
}
Loading