From bbd5447031c0cfb5950859f6f779c4b4e3d037e1 Mon Sep 17 00:00:00 2001 From: Sergey Rymsha Date: Mon, 23 Dec 2024 10:00:13 +0100 Subject: [PATCH] all admin paths should have Cache-control private #10821 (#10825) (cherry picked from commit 161516ce612022684344febb457ea91e663f2213) --- .../xp/admin/impl/app/NoCacheAdminFilter.java | 75 +++++++++++++++ .../impl/app/NoCacheAdminFilterTest.java | 91 +++++++++++++++++++ .../impl/server}/auth/AuthRequiredFilter.java | 2 +- .../xp/impl/server}/auth/BasicAuthFilter.java | 2 +- .../server}/auth/AuthRequiredFilterTest.java | 2 +- .../server}/auth/BasicAuthFilterTest.java | 2 +- 6 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilter.java create mode 100644 modules/admin/admin-impl/src/test/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilterTest.java rename modules/{web/web-impl/src/main/java/com/enonic/xp/web/impl => server/server-rest/src/main/java/com/enonic/xp/impl/server}/auth/AuthRequiredFilter.java (96%) rename modules/{web/web-impl/src/main/java/com/enonic/xp/web/impl => server/server-rest/src/main/java/com/enonic/xp/impl/server}/auth/BasicAuthFilter.java (99%) rename modules/{web/web-impl/src/test/java/com/enonic/xp/web/impl => server/server-rest/src/test/java/com/enonic/xp/impl/server}/auth/AuthRequiredFilterTest.java (98%) rename modules/{web/web-impl/src/test/java/com/enonic/xp/web/impl => server/server-rest/src/test/java/com/enonic/xp/impl/server}/auth/BasicAuthFilterTest.java (99%) diff --git a/modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilter.java b/modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilter.java new file mode 100644 index 00000000000..fb6bfa35dc0 --- /dev/null +++ b/modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilter.java @@ -0,0 +1,75 @@ +package com.enonic.xp.admin.impl.app; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import org.osgi.service.component.annotations.Component; + +import com.google.common.net.HttpHeaders; + +import com.enonic.xp.annotation.Order; +import com.enonic.xp.web.filter.OncePerRequestFilter; + +@Component(immediate = true, service = Filter.class, property = {"connector=xp"}) +@Order(-20) +@WebFilter("/admin/*") +public class NoCacheAdminFilter + extends OncePerRequestFilter +{ + private static final String PRIVATE_NO_CACHE = "private, no-cache"; + + @Override + protected void doHandle( final HttpServletRequest req, final HttpServletResponse res, final FilterChain chain ) + throws Exception + { + res.setHeader( HttpHeaders.CACHE_CONTROL, PRIVATE_NO_CACHE ); + chain.doFilter( req, new NoCacheAdminResponseWrapper( res ) ); + } + + static class NoCacheAdminResponseWrapper + extends HttpServletResponseWrapper + { + public NoCacheAdminResponseWrapper( final HttpServletResponse res ) + { + super( res ); + } + + @Override + public void setHeader( final String name, String value ) + { + if ( HttpHeaders.CACHE_CONTROL.equalsIgnoreCase( name ) ) + { + if ( value != null ) + { + if ( value.contains( "public" ) ) + { + value = value.replaceAll( "public", "private" ); + } + else + { + value = "private, " + value; + } + } + else + { + value = PRIVATE_NO_CACHE; + } + } + super.setHeader( name, value ); + } + + @Override + public void addHeader( final String name, String value ) + { + if ( value != null && HttpHeaders.CACHE_CONTROL.equalsIgnoreCase( name ) ) + { + value = value.replaceAll( "public", "private" ); + } + super.addHeader( name, value ); + } + } +} diff --git a/modules/admin/admin-impl/src/test/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilterTest.java b/modules/admin/admin-impl/src/test/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilterTest.java new file mode 100644 index 00000000000..50ca5e0394c --- /dev/null +++ b/modules/admin/admin-impl/src/test/java/com/enonic/xp/admin/impl/app/NoCacheAdminFilterTest.java @@ -0,0 +1,91 @@ +package com.enonic.xp.admin.impl.app; + + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.google.common.net.HttpHeaders; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class NoCacheAdminFilterTest +{ + private NoCacheAdminFilter noCacheAdminFilter; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Mock + private FilterChain filterChain; + + @BeforeEach + void setUp() + { + MockitoAnnotations.openMocks( this ); + noCacheAdminFilter = new NoCacheAdminFilter(); + } + + @Test + void doHandle_setsCacheControlHeader() + throws Exception + { + noCacheAdminFilter.doHandle( request, response, filterChain ); + + verify( response ).setHeader( HttpHeaders.CACHE_CONTROL, "private, no-cache" ); + verify( filterChain ).doFilter( eq( request ), any( NoCacheAdminFilter.NoCacheAdminResponseWrapper.class ) ); + } + + @Test + void NoCacheAdminResponseWrapper_setHeader_replacesPublicWithPrivate() + { + HttpServletResponse responseWrapper = new NoCacheAdminFilter.NoCacheAdminResponseWrapper( response ); + + responseWrapper.setHeader( HttpHeaders.CACHE_CONTROL, "public, max-age=3600" ); + + verify( response ).setHeader( HttpHeaders.CACHE_CONTROL, "private, max-age=3600" ); + } + + @Test + void NoCacheAdminResponseWrapper_setHeader_addsPrivateIfNotPresent() + { + HttpServletResponse responseWrapper = new NoCacheAdminFilter.NoCacheAdminResponseWrapper( response ); + + responseWrapper.setHeader( HttpHeaders.CACHE_CONTROL, "max-age=3600" ); + + verify( response ).setHeader( HttpHeaders.CACHE_CONTROL, "private, max-age=3600" ); + } + + @Test + void NoCacheAdminResponseWrapper_setHeader_setsPrivateNoCacheIfValueIsNull() + { + HttpServletResponse responseWrapper = new NoCacheAdminFilter.NoCacheAdminResponseWrapper( response ); + + responseWrapper.setHeader( HttpHeaders.CACHE_CONTROL, null ); + + verify( response ).setHeader( HttpHeaders.CACHE_CONTROL, "private, no-cache" ); + } + + @Test + void NoCacheAdminResponseWrapper_addHeader_replacesPublicWithPrivate() + { + HttpServletResponse responseWrapper = new NoCacheAdminFilter.NoCacheAdminResponseWrapper( response ); + + responseWrapper.addHeader( HttpHeaders.CACHE_CONTROL, "public, max-age=3600" ); + + verify( response ).addHeader( HttpHeaders.CACHE_CONTROL, "private, max-age=3600" ); + } +} diff --git a/modules/web/web-impl/src/main/java/com/enonic/xp/web/impl/auth/AuthRequiredFilter.java b/modules/server/server-rest/src/main/java/com/enonic/xp/impl/server/auth/AuthRequiredFilter.java similarity index 96% rename from modules/web/web-impl/src/main/java/com/enonic/xp/web/impl/auth/AuthRequiredFilter.java rename to modules/server/server-rest/src/main/java/com/enonic/xp/impl/server/auth/AuthRequiredFilter.java index 8ef9decfb62..6342974f78b 100644 --- a/modules/web/web-impl/src/main/java/com/enonic/xp/web/impl/auth/AuthRequiredFilter.java +++ b/modules/server/server-rest/src/main/java/com/enonic/xp/impl/server/auth/AuthRequiredFilter.java @@ -1,4 +1,4 @@ -package com.enonic.xp.web.impl.auth; +package com.enonic.xp.impl.server.auth; import javax.servlet.Filter; import javax.servlet.FilterChain; diff --git a/modules/web/web-impl/src/main/java/com/enonic/xp/web/impl/auth/BasicAuthFilter.java b/modules/server/server-rest/src/main/java/com/enonic/xp/impl/server/auth/BasicAuthFilter.java similarity index 99% rename from modules/web/web-impl/src/main/java/com/enonic/xp/web/impl/auth/BasicAuthFilter.java rename to modules/server/server-rest/src/main/java/com/enonic/xp/impl/server/auth/BasicAuthFilter.java index c01c8b824a7..aa89e587896 100644 --- a/modules/web/web-impl/src/main/java/com/enonic/xp/web/impl/auth/BasicAuthFilter.java +++ b/modules/server/server-rest/src/main/java/com/enonic/xp/impl/server/auth/BasicAuthFilter.java @@ -1,4 +1,4 @@ -package com.enonic.xp.web.impl.auth; +package com.enonic.xp.impl.server.auth; import java.nio.charset.StandardCharsets; import java.util.Base64; diff --git a/modules/web/web-impl/src/test/java/com/enonic/xp/web/impl/auth/AuthRequiredFilterTest.java b/modules/server/server-rest/src/test/java/com/enonic/xp/impl/server/auth/AuthRequiredFilterTest.java similarity index 98% rename from modules/web/web-impl/src/test/java/com/enonic/xp/web/impl/auth/AuthRequiredFilterTest.java rename to modules/server/server-rest/src/test/java/com/enonic/xp/impl/server/auth/AuthRequiredFilterTest.java index 070ea154e38..55fa148df45 100644 --- a/modules/web/web-impl/src/test/java/com/enonic/xp/web/impl/auth/AuthRequiredFilterTest.java +++ b/modules/server/server-rest/src/test/java/com/enonic/xp/impl/server/auth/AuthRequiredFilterTest.java @@ -1,4 +1,4 @@ -package com.enonic.xp.web.impl.auth; +package com.enonic.xp.impl.server.auth; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; diff --git a/modules/web/web-impl/src/test/java/com/enonic/xp/web/impl/auth/BasicAuthFilterTest.java b/modules/server/server-rest/src/test/java/com/enonic/xp/impl/server/auth/BasicAuthFilterTest.java similarity index 99% rename from modules/web/web-impl/src/test/java/com/enonic/xp/web/impl/auth/BasicAuthFilterTest.java rename to modules/server/server-rest/src/test/java/com/enonic/xp/impl/server/auth/BasicAuthFilterTest.java index e9977f32505..5fbcc6916ae 100644 --- a/modules/web/web-impl/src/test/java/com/enonic/xp/web/impl/auth/BasicAuthFilterTest.java +++ b/modules/server/server-rest/src/test/java/com/enonic/xp/impl/server/auth/BasicAuthFilterTest.java @@ -1,4 +1,4 @@ -package com.enonic.xp.web.impl.auth; +package com.enonic.xp.impl.server.auth; import java.util.Base64;