diff --git a/src/main/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriter.java b/src/main/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriter.java index 9879788120..f5ed62af9c 100644 --- a/src/main/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriter.java +++ b/src/main/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriter.java @@ -46,9 +46,6 @@ class ContentSecurityPolicyHeaderWriter implements HeaderWriter { private static final String ADD_IMAGE_PAGE_PATTERN = "/series/(add|\\d+|\\d+/(ask|image))"; - // see also spring.h2.console.path in application-test.properties and SecurityConfig - private static final String H2_CONSOLE_PATTERN = "/console/"; - // default policy prevents loading resources from any source private static final String DEFAULT_SRC = "default-src 'none'"; @@ -156,6 +153,8 @@ class ContentSecurityPolicyHeaderWriter implements HeaderWriter { private final boolean useSingleHost; private final boolean hasH2Console; private final String host; + private final String h2ConsolePath; + @Override public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { @@ -168,7 +167,7 @@ public void writeHeaders(HttpServletRequest request, HttpServletResponse respons protected String constructDirectives(String uri) { boolean onCollectionInfoPage = uri.startsWith(COLLECTION_INFO_PAGE_PATTERN); boolean onAddSeriesPage = uri.equals(SeriesUrl.ADD_SERIES_PAGE); - boolean onH2ConsolePage = hasH2Console && uri.startsWith(H2_CONSOLE_PATTERN); + boolean onH2ConsolePage = hasH2Console && uri.startsWith(h2ConsolePath); StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/ru/mystamps/web/support/spring/security/SecurityConfig.java b/src/main/java/ru/mystamps/web/support/spring/security/SecurityConfig.java index beca50cb19..52b7a3de0a 100644 --- a/src/main/java/ru/mystamps/web/support/spring/security/SecurityConfig.java +++ b/src/main/java/ru/mystamps/web/support/spring/security/SecurityConfig.java @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.h2.H2ConsoleProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; import org.springframework.context.ApplicationListener; @@ -72,6 +73,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SiteService siteService; + @Autowired(required = false) + private H2ConsoleProperties h2ConsoleProperties; + @Override @SuppressWarnings("PMD.SignatureDeclareThrowsException") public void configure(WebSecurity web) throws Exception { @@ -89,8 +93,16 @@ protected void configure(HttpSecurity http) throws Exception { boolean usePublicHostname = environment.acceptsProfiles("prod"); String hostname = usePublicHostname ? SiteUrl.PUBLIC_URL : SiteUrl.SITE; + String h2ConsolePath = hasH2Console ? h2ConsoleProperties.getPath() : null; + + // Allow unsecured requests to H2 consoles if available. + // See also spring.h2.console.path in application-test.properties + String[] pathsToIgnore = + hasH2Console ? new String[]{h2ConsolePath + "/**", SiteUrl.CSP_REPORTS_HANDLER} + : new String[]{SiteUrl.CSP_REPORTS_HANDLER}; + ContentSecurityPolicyHeaderWriter cspWriter = - new ContentSecurityPolicyHeaderWriter(useCdn, useSingleHost, hasH2Console, hostname); + new ContentSecurityPolicyHeaderWriter(useCdn, useSingleHost, hasH2Console, hostname, h2ConsolePath); http .authorizeRequests() @@ -138,10 +150,7 @@ protected void configure(HttpSecurity http) throws Exception { .authenticationEntryPoint(new Http403ForbiddenEntryPoint()) .and() .csrf() - // Allow unsecured requests to H2 consoles. - // See also spring.h2.console.path in application-test.properties and - // ContentSecurityPolicyHeaderWriter.H2_CONSOLE_PATTERN - .ignoringAntMatchers("/console/**", SiteUrl.CSP_REPORTS_HANDLER) + .ignoringAntMatchers(pathsToIgnore) .and() .rememberMe() // FIXME: GH #27 diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 4140c9da2b..df700eef23 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -7,7 +7,7 @@ spring.datasource.driver-class-name: org.h2.Driver spring.datasource.initialization-mode: NEVER spring.h2.console.enabled: true -# see also SecurityConfig and ContentSecurityPolicyHeaderWriter.H2_CONSOLE_PATTERN +# see also SecurityConfig spring.h2.console.path: /console # required for using /console with CSP because we have many hashes as a workaround diff --git a/src/test/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriterTest.java b/src/test/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriterTest.java index 0f3ed8b4d7..c850060016 100644 --- a/src/test/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriterTest.java +++ b/src/test/java/ru/mystamps/web/support/spring/security/ContentSecurityPolicyHeaderWriterTest.java @@ -38,7 +38,8 @@ public class ContentSecurityPolicyHeaderWriterTest implements WithAssertions { private static final int NUMBER_OF_DIRECTIVES_ON_ADD_SERIES_PAGE = 7; private static final int NUMBER_OF_DIRECTIVES_ON_INFO_SERIES_PAGE = 7; private static final int NUMBER_OF_DIRECTIVES_ON_H2_CONSOLE_PAGE = 7; - + private static final String H2_CONSOLE_PATH = "/console/"; + @Rule public TogglzRule togglz = TogglzRule.allEnabled(Features.class); @@ -49,8 +50,13 @@ public class ContentSecurityPolicyHeaderWriterTest implements WithAssertions { @Test public void writeContentSecurityPolicyHeader() { // given - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(bool(), bool(), bool(), Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + bool(), + bool(), + bool(), + Random.host(), + H2_CONSOLE_PATH + ); HttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = new MockHttpServletResponse(); @@ -76,8 +82,13 @@ public void writeContentSecurityPolicyHeader() { @Test public void onIndexPageWithLocalResources() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(false, true, bool(), SiteUrl.SITE); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + false, + true, + bool(), + SiteUrl.SITE, + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/").split(";"); assertThat(directives) @@ -91,11 +102,16 @@ public void onIndexPageWithLocalResources() { ) .hasSize(NUMBER_OF_DIRECTIVES_ON_STANDARD_PAGES); } - + @Test public void onIndexPageWithResourcesFromCdn() { - ContentSecurityPolicyHeaderWriter writer - = new ContentSecurityPolicyHeaderWriter(true, false, bool(), SiteUrl.PUBLIC_URL); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + true, + false, + bool(), + SiteUrl.PUBLIC_URL, + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/").split(";"); assertThat(directives) @@ -125,8 +141,13 @@ public void onIndexPageWithResourcesFromCdn() { @Test public void onCollectionInfoPageWithLocalResources() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(false, true, bool(), Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + false, + true, + bool(), + Random.host(), + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/collection/user").split(";"); // test only the directives that differ from the index page @@ -152,8 +173,13 @@ public void onCollectionInfoPageWithLocalResources() { @Test public void onCollectionInfoPageWithResourcesFromCdn() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(true, false, bool(), Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + true, + false, + bool(), + Random.host(), + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/collection/user").split(";"); // test only the directives that differ from the index page @@ -182,8 +208,13 @@ public void onCollectionInfoPageWithResourcesFromCdn() { @Test public void onSeriesAddImagePageWithLocalResources() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(false, true, bool(), Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + false, + true, + bool(), + Random.host(), + H2_CONSOLE_PATH + ); for (String page : new String[]{"/series/11", "/series/12/ask", "/series/13/image"}) { String[] directives = writer.constructDirectives(page).split(";"); @@ -205,8 +236,13 @@ public void onSeriesAddImagePageWithLocalResources() { @Test public void onSeriesAddImagePageWithResourcesFromCdn() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(true, false, bool(), Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + true, + false, + bool(), + Random.host(), + H2_CONSOLE_PATH + ); for (String page : new String[]{"/series/11", "/series/12/ask", "/series/13/image"}) { String[] directives = writer.constructDirectives(page).split(";"); @@ -238,8 +274,13 @@ public void onSeriesAddImagePageWithResourcesFromCdn() { @Test public void onSeriesAddPageWithLocalResources() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(false, true, bool(), Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + false, + true, + bool(), + Random.host(), + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/series/add").split(";"); // test only the directives that differ from the index page @@ -266,8 +307,13 @@ public void onSeriesAddPageWithLocalResources() { @Test public void onSeriesAddPageWithResourcesFromCdn() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(true, false, bool(), Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + true, + false, + bool(), + Random.host(), + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/series/add").split(";"); // test only the directives that differ from the index page @@ -297,8 +343,13 @@ public void onSeriesAddPageWithResourcesFromCdn() { @Test public void onH2ConsoleWithLocalResources() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(false, true, true, Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + false, + true, + true, + Random.host(), + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/console/").split(";"); // test only the directives that are differ from the index page @@ -325,8 +376,13 @@ public void onH2ConsoleWithLocalResources() { @Test public void onH2ConsoleWithResourcesFromCdn() { - ContentSecurityPolicyHeaderWriter writer = - new ContentSecurityPolicyHeaderWriter(true, false, false, Random.host()); + ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter( + true, + false, + false, + Random.host(), + H2_CONSOLE_PATH + ); String[] directives = writer.constructDirectives("/console/").split(";"); assertThat(directives)