From 0beb725259fc43578fbae401e73e4c9e6123ab76 Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Fri, 3 Dec 2021 16:47:21 -0300 Subject: [PATCH] Add Cross Origin Policies headers Add DSL support for Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Cross-Origin-Resource-Policy headers Closes gh-9385, gh-10118 --- .../web/configurers/HeadersConfigurer.java | 229 +++++++++++++++++- .../http/HeadersBeanDefinitionParser.java | 83 ++++++- .../config/web/server/ServerHttpSecurity.java | 186 +++++++++++++- .../config/annotation/web/HeadersDsl.kt | 60 +++++ .../headers/CrossOriginEmbedderPolicyDsl.kt | 43 ++++ .../web/headers/CrossOriginOpenerPolicyDsl.kt | 43 ++++ .../headers/CrossOriginResourcePolicyDsl.kt | 43 ++++ .../ServerCrossOriginEmbedderPolicyDsl.kt | 42 ++++ .../ServerCrossOriginOpenerPolicyDsl.kt | 42 ++++ .../ServerCrossOriginResourcePolicyDsl.kt | 42 ++++ .../config/web/server/ServerHeadersDsl.kt | 52 +++- .../security/config/spring-security-6.0.rnc | 23 +- .../security/config/spring-security-6.0.xsd | 74 ++++++ .../configurers/HeadersConfigurerTests.java | 78 +++++- .../config/http/HttpHeadersConfigTests.java | 50 +++- .../config/web/server/HeaderSpecTests.java | 53 +++- .../config/annotation/web/HeadersDslTests.kt | 1 + .../web/server/ServerHeadersDslTests.kt | 59 +++++ ...sDisabledWithCrossOriginEmbedderPolicy.xml | 36 +++ ...ltsDisabledWithCrossOriginOpenerPolicy.xml | 36 +++ ...efaultsDisabledWithCrossOriginPolicies.xml | 38 +++ ...sDisabledWithCrossOriginResourcePolicy.xml | 36 +++ .../ROOT/pages/features/exploits/headers.adoc | 20 ++ .../ROOT/pages/reactive/exploits/headers.adoc | 62 +++++ .../servlet/appendix/namespace/http.adoc | 66 +++++ .../ROOT/pages/servlet/exploits/headers.adoc | 61 +++++ ...CrossOriginEmbedderPolicyHeaderWriter.java | 84 +++++++ .../CrossOriginOpenerPolicyHeaderWriter.java | 86 +++++++ ...CrossOriginResourcePolicyHeaderWriter.java | 86 +++++++ ...EmbedderPolicyServerHttpHeadersWriter.java | 78 ++++++ ...inOpenerPolicyServerHttpHeadersWriter.java | 80 ++++++ ...ResourcePolicyServerHttpHeadersWriter.java | 80 ++++++ ...OriginEmbedderPolicyHeaderWriterTests.java | 80 ++++++ ...ssOriginOpenerPolicyHeaderWriterTests.java | 80 ++++++ ...OriginResourcePolicyHeaderWriterTests.java | 80 ++++++ ...derPolicyServerHttpHeadersWriterTests.java | 76 ++++++ ...nerPolicyServerHttpHeadersWriterTests.java | 77 ++++++ ...rcePolicyServerHttpHeadersWriterTests.java | 76 ++++++ 38 files changed, 2513 insertions(+), 8 deletions(-) create mode 100644 config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginEmbedderPolicyDsl.kt create mode 100644 config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginOpenerPolicyDsl.kt create mode 100644 config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginResourcePolicyDsl.kt create mode 100644 config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginEmbedderPolicyDsl.kt create mode 100644 config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginOpenerPolicyDsl.kt create mode 100644 config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginResourcePolicyDsl.kt create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginEmbedderPolicy.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginOpenerPolicy.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginPolicies.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginResourcePolicy.xml create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java create mode 100644 web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java create mode 100644 web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java create mode 100644 web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java create mode 100644 web/src/test/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriterTests.java create mode 100644 web/src/test/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriterTests.java create mode 100644 web/src/test/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriterTests.java create mode 100644 web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java create mode 100644 web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java create mode 100644 web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java index 9a69cc63862..0ad06d2274f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,9 @@ import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.security.web.header.writers.CacheControlHeadersWriter; import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter; import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter; import org.springframework.security.web.header.writers.HpkpHeaderWriter; import org.springframework.security.web.header.writers.HstsHeaderWriter; @@ -97,6 +100,12 @@ public class HeadersConfigurer> private final PermissionsPolicyConfig permissionsPolicy = new PermissionsPolicyConfig(); + private final CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy = new CrossOriginOpenerPolicyConfig(); + + private final CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyConfig(); + + private final CrossOriginResourcePolicyConfig crossOriginResourcePolicy = new CrossOriginResourcePolicyConfig(); + /** * Creates a new instance * @@ -392,6 +401,9 @@ private List getHeaderWriters() { addIfNotNull(writers, this.referrerPolicy.writer); addIfNotNull(writers, this.featurePolicy.writer); addIfNotNull(writers, this.permissionsPolicy.writer); + addIfNotNull(writers, this.crossOriginOpenerPolicy.writer); + addIfNotNull(writers, this.crossOriginEmbedderPolicy.writer); + addIfNotNull(writers, this.crossOriginResourcePolicy.writer); writers.addAll(this.headerWriters); return writers; } @@ -544,6 +556,129 @@ public PermissionsPolicyConfig permissionsPolicy(Customizer + * Cross-Origin-Opener-Policy header. + *

+ * Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which + * responsible for writing the header. + *

+ * @return the {@link CrossOriginOpenerPolicyConfig} for additional confniguration + * @since 5.7 + * @see CrossOriginOpenerPolicyHeaderWriter + */ + public CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy() { + this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter(); + return this.crossOriginOpenerPolicy; + } + + /** + * Allows configuration for + * Cross-Origin-Opener-Policy header. + *

+ * Calling this method automatically enables (includes) the + * {@code Cross-Origin-Opener-Policy} header in the response using the supplied + * policy. + *

+ *

+ * Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which + * responsible for writing the header. + *

+ * @return the {@link HeadersConfigurer} for additional customizations + * @since 5.7 + * @see CrossOriginOpenerPolicyHeaderWriter + */ + public HeadersConfigurer crossOriginOpenerPolicy( + Customizer crossOriginOpenerPolicyCustomizer) { + this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter(); + crossOriginOpenerPolicyCustomizer.customize(this.crossOriginOpenerPolicy); + return HeadersConfigurer.this; + } + + /** + * Allows configuration for + * Cross-Origin-Embedder-Policy header. + *

+ * Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter} + * which is responsible for writing the header. + *

+ * @return the {@link CrossOriginEmbedderPolicyConfig} for additional customizations + * @since 5.7 + * @see CrossOriginEmbedderPolicyHeaderWriter + */ + public CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy() { + this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter(); + return this.crossOriginEmbedderPolicy; + } + + /** + * Allows configuration for + * Cross-Origin-Embedder-Policy header. + *

+ * Calling this method automatically enables (includes) the + * {@code Cross-Origin-Embedder-Policy} header in the response using the supplied + * policy. + *

+ *

+ * Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter} + * which is responsible for writing the header. + *

+ * @return the {@link HeadersConfigurer} for additional customizations + * @since 5.7 + * @see CrossOriginEmbedderPolicyHeaderWriter + */ + public HeadersConfigurer crossOriginEmbedderPolicy( + Customizer crossOriginEmbedderPolicyCustomizer) { + this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter(); + crossOriginEmbedderPolicyCustomizer.customize(this.crossOriginEmbedderPolicy); + return HeadersConfigurer.this; + } + + /** + * Allows configuration for + * Cross-Origin-Resource-Policy header. + *

+ * Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter} + * which is responsible for writing the header: + *

+ * @return the {@link HeadersConfigurer} for additional customizations + * @since 5.7 + * @see CrossOriginResourcePolicyHeaderWriter + */ + public CrossOriginResourcePolicyConfig crossOriginResourcePolicy() { + this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter(); + return this.crossOriginResourcePolicy; + } + + /** + * Allows configuration for + * Cross-Origin-Resource-Policy header. + *

+ * Calling this method automatically enables (includes) the + * {@code Cross-Origin-Resource-Policy} header in the response using the supplied + * policy. + *

+ *

+ * Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter} + * which is responsible for writing the header: + *

+ * @return the {@link HeadersConfigurer} for additional customizations + * @since 5.7 + * @see CrossOriginResourcePolicyHeaderWriter + */ + public HeadersConfigurer crossOriginResourcePolicy( + Customizer crossOriginResourcePolicyCustomizer) { + this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter(); + crossOriginResourcePolicyCustomizer.customize(this.crossOriginResourcePolicy); + return HeadersConfigurer.this; + } + public final class ContentTypeOptionsConfig { private XContentTypeOptionsHeaderWriter writer; @@ -1142,4 +1277,96 @@ public HeadersConfigurer and() { } + public final class CrossOriginOpenerPolicyConfig { + + private CrossOriginOpenerPolicyHeaderWriter writer; + + public CrossOriginOpenerPolicyConfig() { + } + + /** + * Sets the policy to be used in the {@code Cross-Origin-Opener-Policy} header + * @param openerPolicy a {@code Cross-Origin-Opener-Policy} + * @return the {@link CrossOriginOpenerPolicyConfig} for additional configuration + * @throws IllegalArgumentException if openerPolicy is null + */ + public CrossOriginOpenerPolicyConfig policy( + CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy openerPolicy) { + this.writer.setPolicy(openerPolicy); + return this; + } + + /** + * Allows completing configuration of Cross Origin Opener Policy and continuing + * configuration of headers. + * @return the {@link HeadersConfigurer} for additional configuration + */ + public HeadersConfigurer and() { + return HeadersConfigurer.this; + } + + } + + public final class CrossOriginEmbedderPolicyConfig { + + private CrossOriginEmbedderPolicyHeaderWriter writer; + + public CrossOriginEmbedderPolicyConfig() { + } + + /** + * Sets the policy to be used in the {@code Cross-Origin-Embedder-Policy} header + * @param embedderPolicy a {@code Cross-Origin-Embedder-Policy} + * @return the {@link CrossOriginEmbedderPolicyConfig} for additional + * configuration + * @throws IllegalArgumentException if embedderPolicy is null + */ + public CrossOriginEmbedderPolicyConfig policy( + CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy embedderPolicy) { + this.writer.setPolicy(embedderPolicy); + return this; + } + + /** + * Allows completing configuration of Cross-Origin-Embedder-Policy and continuing + * configuration of headers. + * @return the {@link HeadersConfigurer} for additional configuration + */ + public HeadersConfigurer and() { + return HeadersConfigurer.this; + } + + } + + public final class CrossOriginResourcePolicyConfig { + + private CrossOriginResourcePolicyHeaderWriter writer; + + public CrossOriginResourcePolicyConfig() { + } + + /** + * Sets the policy to be used in the {@code Cross-Origin-Resource-Policy} header + * @param resourcePolicy a {@code Cross-Origin-Resource-Policy} + * @return the {@link CrossOriginResourcePolicyConfig} for additional + * configuration + * @throws IllegalArgumentException if resourcePolicy is null + */ + public CrossOriginResourcePolicyConfig policy( + CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy resourcePolicy) { + this.writer.setPolicy(resourcePolicy); + return this; + } + + /** + * Allows completing configuration of Cross-Origin-Resource-Policy and continuing + * configuration of headers. + * @return the {@link HeadersConfigurer} for additional configuration + */ + public HeadersConfigurer and() { + return HeadersConfigurer.this; + } + + } + } diff --git a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java index 7f42ff724e2..b980f635a73 100644 --- a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,9 @@ import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.security.web.header.writers.CacheControlHeadersWriter; import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter; import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter; import org.springframework.security.web.header.writers.HpkpHeaderWriter; import org.springframework.security.web.header.writers.HstsHeaderWriter; @@ -122,6 +125,12 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { private static final String PERMISSIONS_POLICY_ELEMENT = "permissions-policy"; + private static final String CROSS_ORIGIN_OPENER_POLICY_ELEMENT = "cross-origin-opener-policy"; + + private static final String CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT = "cross-origin-embedder-policy"; + + private static final String CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT = "cross-origin-resource-policy"; + private static final String ALLOW_FROM = "ALLOW-FROM"; private ManagedList headerWriters; @@ -144,6 +153,9 @@ public BeanDefinition parse(Element element, ParserContext parserContext) { parseReferrerPolicyElement(element, parserContext); parseFeaturePolicyElement(element, parserContext); parsePermissionsPolicyElement(element, parserContext); + parseCrossOriginOpenerPolicy(disabled, element); + parseCrossOriginEmbedderPolicy(disabled, element); + parseCrossOriginResourcePolicy(disabled, element); parseHeaderElements(element); boolean noWriters = this.headerWriters.isEmpty(); if (disabled && !noWriters) { @@ -376,6 +388,75 @@ private void addPermissionsPolicy(Element permissionsPolicyElement, ParserContex this.headerWriters.add(headersWriter.getBeanDefinition()); } + private void parseCrossOriginOpenerPolicy(boolean elementDisabled, Element element) { + if (elementDisabled || element == null) { + return; + } + CrossOriginOpenerPolicyHeaderWriter writer = new CrossOriginOpenerPolicyHeaderWriter(); + Element crossOriginOpenerPolicyElement = DomUtils.getChildElementByTagName(element, + CROSS_ORIGIN_OPENER_POLICY_ELEMENT); + if (crossOriginOpenerPolicyElement != null) { + addCrossOriginOpenerPolicy(crossOriginOpenerPolicyElement, writer); + } + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(CrossOriginOpenerPolicyHeaderWriter.class, () -> writer); + this.headerWriters.add(builder.getBeanDefinition()); + } + + private void parseCrossOriginEmbedderPolicy(boolean elementDisabled, Element element) { + if (elementDisabled || element == null) { + return; + } + CrossOriginEmbedderPolicyHeaderWriter writer = new CrossOriginEmbedderPolicyHeaderWriter(); + Element crossOriginEmbedderPolicyElement = DomUtils.getChildElementByTagName(element, + CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT); + if (crossOriginEmbedderPolicyElement != null) { + addCrossOriginEmbedderPolicy(crossOriginEmbedderPolicyElement, writer); + } + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(CrossOriginEmbedderPolicyHeaderWriter.class, () -> writer); + this.headerWriters.add(builder.getBeanDefinition()); + } + + private void parseCrossOriginResourcePolicy(boolean elementDisabled, Element element) { + if (elementDisabled || element == null) { + return; + } + CrossOriginResourcePolicyHeaderWriter writer = new CrossOriginResourcePolicyHeaderWriter(); + Element crossOriginResourcePolicyElement = DomUtils.getChildElementByTagName(element, + CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT); + if (crossOriginResourcePolicyElement != null) { + addCrossOriginResourcePolicy(crossOriginResourcePolicyElement, writer); + } + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(CrossOriginResourcePolicyHeaderWriter.class, () -> writer); + this.headerWriters.add(builder.getBeanDefinition()); + } + + private void addCrossOriginResourcePolicy(Element crossOriginResourcePolicyElement, + CrossOriginResourcePolicyHeaderWriter writer) { + String policy = crossOriginResourcePolicyElement.getAttribute(ATT_POLICY); + if (StringUtils.hasText(policy)) { + writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.from(policy)); + } + } + + private void addCrossOriginEmbedderPolicy(Element crossOriginEmbedderPolicyElement, + CrossOriginEmbedderPolicyHeaderWriter writer) { + String policy = crossOriginEmbedderPolicyElement.getAttribute(ATT_POLICY); + if (StringUtils.hasText(policy)) { + writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.from(policy)); + } + } + + private void addCrossOriginOpenerPolicy(Element crossOriginOpenerPolicyElement, + CrossOriginOpenerPolicyHeaderWriter writer) { + String policy = crossOriginOpenerPolicyElement.getAttribute(ATT_POLICY); + if (StringUtils.hasText(policy)) { + writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.from(policy)); + } + } + private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) { context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.", element); diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 8e3d7861a1c..5c0094f079a 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -149,6 +149,12 @@ import org.springframework.security.web.server.header.CompositeServerHttpHeadersWriter; import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter; +import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter; +import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy; +import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter; +import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy; +import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter; +import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy; import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter; import org.springframework.security.web.server.header.PermissionsPolicyServerHttpHeadersWriter; @@ -2380,10 +2386,17 @@ public final class HeaderSpec { private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter(); + private CrossOriginOpenerPolicyServerHttpHeadersWriter crossOriginOpenerPolicy = new CrossOriginOpenerPolicyServerHttpHeadersWriter(); + + private CrossOriginEmbedderPolicyServerHttpHeadersWriter crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyServerHttpHeadersWriter(); + + private CrossOriginResourcePolicyServerHttpHeadersWriter crossOriginResourcePolicy = new CrossOriginResourcePolicyServerHttpHeadersWriter(); + private HeaderSpec() { this.writers = new ArrayList<>(Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts, this.frameOptions, this.xss, this.featurePolicy, this.permissionsPolicy, this.contentSecurityPolicy, - this.referrerPolicy)); + this.referrerPolicy, this.crossOriginOpenerPolicy, this.crossOriginEmbedderPolicy, + this.crossOriginResourcePolicy)); } /** @@ -2595,6 +2608,84 @@ public HeaderSpec referrerPolicy(Customizer referrerPolicyCu return this; } + /** + * Configures the + * Cross-Origin-Opener-Policy header. + * @return the {@link CrossOriginOpenerPolicySpec} to configure + * @since 5.7 + * @see CrossOriginOpenerPolicyServerHttpHeadersWriter + */ + public CrossOriginOpenerPolicySpec crossOriginOpenerPolicy() { + return new CrossOriginOpenerPolicySpec(); + } + + /** + * Configures the + * Cross-Origin-Opener-Policy header. + * @return the {@link HeaderSpec} to customize + * @since 5.7 + * @see CrossOriginOpenerPolicyServerHttpHeadersWriter + */ + public HeaderSpec crossOriginOpenerPolicy( + Customizer crossOriginOpenerPolicyCustomizer) { + crossOriginOpenerPolicyCustomizer.customize(new CrossOriginOpenerPolicySpec()); + return this; + } + + /** + * Configures the + * Cross-Origin-Embedder-Policy header. + * @return the {@link CrossOriginEmbedderPolicySpec} to configure + * @since 5.7 + * @see CrossOriginEmbedderPolicyServerHttpHeadersWriter + */ + public CrossOriginEmbedderPolicySpec crossOriginEmbedderPolicy() { + return new CrossOriginEmbedderPolicySpec(); + } + + /** + * Configures the + * Cross-Origin-Embedder-Policy header. + * @return the {@link HeaderSpec} to customize + * @since 5.7 + * @see CrossOriginEmbedderPolicyServerHttpHeadersWriter + */ + public HeaderSpec crossOriginEmbedderPolicy( + Customizer crossOriginEmbedderPolicyCustomizer) { + crossOriginEmbedderPolicyCustomizer.customize(new CrossOriginEmbedderPolicySpec()); + return this; + } + + /** + * Configures the + * Cross-Origin-Resource-Policy header. + * @return the {@link CrossOriginResourcePolicySpec} to configure + * @since 5.7 + * @see CrossOriginResourcePolicyServerHttpHeadersWriter + */ + public CrossOriginResourcePolicySpec crossOriginResourcePolicy() { + return new CrossOriginResourcePolicySpec(); + } + + /** + * Configures the + * Cross-Origin-Resource-Policy header. + * @return the {@link HeaderSpec} to customize + * @since 5.7 + * @see CrossOriginResourcePolicyServerHttpHeadersWriter + */ + public HeaderSpec crossOriginResourcePolicy( + Customizer crossOriginResourcePolicyCustomizer) { + crossOriginResourcePolicyCustomizer.customize(new CrossOriginResourcePolicySpec()); + return this; + } + /** * Configures cache control headers * @@ -2910,6 +3001,99 @@ public HeaderSpec and() { } + /** + * Configures the Cross-Origin-Opener-Policy header + * + * @since 5.7 + */ + public final class CrossOriginOpenerPolicySpec { + + private CrossOriginOpenerPolicySpec() { + } + + /** + * Sets the value to be used in the `Cross-Origin-Opener-Policy` header + * @param openerPolicy a opener policy + * @return the {@link CrossOriginOpenerPolicySpec} to continue configuring + */ + public CrossOriginOpenerPolicySpec policy(CrossOriginOpenerPolicy openerPolicy) { + HeaderSpec.this.crossOriginOpenerPolicy.setPolicy(openerPolicy); + return this; + } + + /** + * Allows method chaining to continue configuring the + * {@link ServerHttpSecurity}. + * @return the {@link HeaderSpec} to continue configuring + */ + public HeaderSpec and() { + return HeaderSpec.this; + } + + } + + /** + * Configures the Cross-Origin-Embedder-Policy header + * + * @since 5.7 + */ + public final class CrossOriginEmbedderPolicySpec { + + private CrossOriginEmbedderPolicySpec() { + } + + /** + * Sets the value to be used in the `Cross-Origin-Embedder-Policy` header + * @param embedderPolicy a opener policy + * @return the {@link CrossOriginEmbedderPolicySpec} to continue configuring + */ + public CrossOriginEmbedderPolicySpec policy(CrossOriginEmbedderPolicy embedderPolicy) { + HeaderSpec.this.crossOriginEmbedderPolicy.setPolicy(embedderPolicy); + return this; + } + + /** + * Allows method chaining to continue configuring the + * {@link ServerHttpSecurity}. + * @return the {@link HeaderSpec} to continue configuring + */ + public HeaderSpec and() { + return HeaderSpec.this; + } + + } + + /** + * Configures the Cross-Origin-Resource-Policy header + * + * @since 5.7 + */ + public final class CrossOriginResourcePolicySpec { + + private CrossOriginResourcePolicySpec() { + } + + /** + * Sets the value to be used in the `Cross-Origin-Resource-Policy` header + * @param resourcePolicy a opener policy + * @return the {@link CrossOriginResourcePolicySpec} to continue configuring + */ + public CrossOriginResourcePolicySpec policy(CrossOriginResourcePolicy resourcePolicy) { + HeaderSpec.this.crossOriginResourcePolicy.setPolicy(resourcePolicy); + return this; + } + + /** + * Allows method chaining to continue configuring the + * {@link ServerHttpSecurity}. + * @return the {@link HeaderSpec} to continue configuring + */ + public HeaderSpec and() { + return HeaderSpec.this; + } + + } + } /** diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HeadersDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HeadersDsl.kt index c4f5aa2b15d..a468be3b23e 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HeadersDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HeadersDsl.kt @@ -42,6 +42,9 @@ class HeadersDsl { private var referrerPolicy: ((HeadersConfigurer.ReferrerPolicyConfig) -> Unit)? = null private var featurePolicyDirectives: String? = null private var permissionsPolicy: ((HeadersConfigurer.PermissionsPolicyConfig) -> Unit)? = null + private var crossOriginOpenerPolicy: ((HeadersConfigurer.CrossOriginOpenerPolicyConfig) -> Unit)? = null + private var crossOriginEmbedderPolicy: ((HeadersConfigurer.CrossOriginEmbedderPolicyConfig) -> Unit)? = null + private var crossOriginResourcePolicy: ((HeadersConfigurer.CrossOriginResourcePolicyConfig) -> Unit)? = null private var disabled = false private var headerWriters = mutableListOf() @@ -181,6 +184,54 @@ class HeadersDsl { this.permissionsPolicy = PermissionsPolicyDsl().apply(permissionsPolicyConfig).get() } + /** + * Allows configuration for + * Cross-Origin-Opener-Policy header. + * + *

+ * Calling this method automatically enables (includes) the Cross-Origin-Opener-Policy + * header in the response using the supplied policy. + *

+ * + * @since 5.7 + * @param crossOriginOpenerPolicyConfig the customization to apply to the header + */ + fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: CrossOriginOpenerPolicyDsl.() -> Unit) { + this.crossOriginOpenerPolicy = CrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get() + } + + /** + * Allows configuration for + * Cross-Origin-Embedder-Policy header. + * + *

+ * Calling this method automatically enables (includes) the Cross-Origin-Embedder-Policy + * header in the response using the supplied policy. + *

+ * + * @since 5.7 + * @param crossOriginEmbedderPolicyConfig the customization to apply to the header + */ + fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: CrossOriginEmbedderPolicyDsl.() -> Unit) { + this.crossOriginEmbedderPolicy = CrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get() + } + + /** + * Configures the + * Cross-Origin-Resource-Policy header. + * + *

+ * Calling this method automatically enables (includes) the Cross-Origin-Resource-Policy + * header in the response using the supplied policy. + *

+ * + * @since 5.7 + * @param crossOriginResourcePolicyConfig the customization to apply to the header + */ + fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: CrossOriginResourcePolicyDsl.() -> Unit) { + this.crossOriginResourcePolicy = CrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get() + } + /** * Adds a [HeaderWriter] instance. * @@ -238,6 +289,15 @@ class HeadersDsl { permissionsPolicy?.also { headers.permissionsPolicy(permissionsPolicy) } + crossOriginOpenerPolicy?.also { + headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy) + } + crossOriginEmbedderPolicy?.also { + headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy) + } + crossOriginResourcePolicy?.also { + headers.crossOriginResourcePolicy(crossOriginResourcePolicy) + } headerWriters.forEach { headerWriter -> headers.addHeaderWriter(headerWriter) } diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginEmbedderPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginEmbedderPolicyDsl.kt new file mode 100644 index 00000000000..f24c33ec933 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginEmbedderPolicyDsl.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web.headers + +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer +import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter + +/** + * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using + * idiomatic Kotlin code. + * + * @author Marcus Da Coregio + * @since 5.7 + * @property policy the policy to be used in the response header. + */ +@HeadersSecurityMarker +class CrossOriginEmbedderPolicyDsl { + + var policy: CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy? = null + + internal fun get(): (HeadersConfigurer.CrossOriginEmbedderPolicyConfig) -> Unit { + return { crossOriginEmbedderPolicy -> + policy?.also { + crossOriginEmbedderPolicy.policy(policy) + } + } + } +} diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginOpenerPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginOpenerPolicyDsl.kt new file mode 100644 index 00000000000..2a9fc2a3b8e --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginOpenerPolicyDsl.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web.headers + +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer +import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter + +/** + * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using + * idiomatic Kotlin code. + * + * @author Marcus Da Coregio + * @since 5.7 + * @property policy the policy to be used in the response header. + */ +@HeadersSecurityMarker +class CrossOriginOpenerPolicyDsl { + + var policy: CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy? = null + + internal fun get(): (HeadersConfigurer.CrossOriginOpenerPolicyConfig) -> Unit { + return { crossOriginOpenerPolicy -> + policy?.also { + crossOriginOpenerPolicy.policy(policy) + } + } + } +} diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginResourcePolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginResourcePolicyDsl.kt new file mode 100644 index 00000000000..cd5fbe03e74 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/headers/CrossOriginResourcePolicyDsl.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web.headers + +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer +import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter + +/** + * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using + * idiomatic Kotlin code. + * + * @author Marcus Da Coregio + * @since 5.7 + * @property policy the policy to be used in the response header. + */ +@HeadersSecurityMarker +class CrossOriginResourcePolicyDsl { + + var policy: CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy? = null + + internal fun get(): (HeadersConfigurer.CrossOriginResourcePolicyConfig) -> Unit { + return { crossOriginResourcePolicy -> + policy?.also { + crossOriginResourcePolicy.policy(policy) + } + } + } +} diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginEmbedderPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginEmbedderPolicyDsl.kt new file mode 100644 index 00000000000..cf5ae7ec9d9 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginEmbedderPolicyDsl.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.web.server + +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter + +/** + * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using + * idiomatic Kotlin code. + * + * @author Marcus Da Coregio + * @since 5.7 + * @property policy the policy to be used in the response header. + */ +@ServerSecurityMarker +class ServerCrossOriginEmbedderPolicyDsl { + + var policy: CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy? = null + + internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit { + return { crossOriginEmbedderPolicy -> + policy?.also { + crossOriginEmbedderPolicy.policy(policy) + } + } + } +} diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginOpenerPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginOpenerPolicyDsl.kt new file mode 100644 index 00000000000..70d6576c837 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginOpenerPolicyDsl.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.web.server + +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter + +/** + * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using + * idiomatic Kotlin code. + * + * @author Marcus Da Coregio + * @since 5.7 + * @property policy the policy to be used in the response header. + */ +@ServerSecurityMarker +class ServerCrossOriginOpenerPolicyDsl { + + var policy: CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy? = null + + internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit { + return { crossOriginOpenerPolicy -> + policy?.also { + crossOriginOpenerPolicy.policy(policy) + } + } + } +} diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginResourcePolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginResourcePolicyDsl.kt new file mode 100644 index 00000000000..580ee355ee7 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginResourcePolicyDsl.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.web.server + +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter + +/** + * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using + * idiomatic Kotlin code. + * + * @author Marcus Da Coregio + * @since 5.7 + * @property policy the policy to be used in the response header. + */ +@ServerSecurityMarker +class ServerCrossOriginResourcePolicyDsl { + + var policy: CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy? = null + + internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit { + return { crossOriginResourcePolicy -> + policy?.also { + crossOriginResourcePolicy.policy(policy) + } + } + } +} diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt index f38b1527218..37bd1f177a9 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt @@ -16,7 +16,12 @@ package org.springframework.security.config.web.server -import org.springframework.security.web.server.header.* +import org.springframework.security.web.server.header.CacheControlServerHttpHeadersWriter +import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter +import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter +import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter +import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter +import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter /** * A Kotlin DSL to configure [ServerHttpSecurity] headers using idiomatic Kotlin code. @@ -35,6 +40,9 @@ class ServerHeadersDsl { private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null private var featurePolicyDirectives: String? = null private var permissionsPolicy: ((ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit)? = null + private var crossOriginOpenerPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit)? = null + private var crossOriginEmbedderPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit)? = null + private var crossOriginResourcePolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit)? = null private var disabled = false @@ -157,6 +165,39 @@ class ServerHeadersDsl { this.permissionsPolicy = ServerPermissionsPolicyDsl().apply(permissionsPolicyConfig).get() } + /** + * Allows configuration for + * Cross-Origin-Opener-Policy header. + * + * @since 5.7 + * @param crossOriginOpenerPolicyConfig the customization to apply to the header + */ + fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: ServerCrossOriginOpenerPolicyDsl.() -> Unit) { + this.crossOriginOpenerPolicy = ServerCrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get() + } + + /** + * Allows configuration for + * Cross-Origin-Embedder-Policy header. + * + * @since 5.7 + * @param crossOriginEmbedderPolicyConfig the customization to apply to the header + */ + fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: ServerCrossOriginEmbedderPolicyDsl.() -> Unit) { + this.crossOriginEmbedderPolicy = ServerCrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get() + } + + /** + * Allows configuration for + * Cross-Origin-Resource-Policy header. + * + * @since 5.7 + * @param crossOriginResourcePolicyConfig the customization to apply to the header + */ + fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: ServerCrossOriginResourcePolicyDsl.() -> Unit) { + this.crossOriginResourcePolicy = ServerCrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get() + } + /** * Disables HTTP response headers. */ @@ -194,6 +235,15 @@ class ServerHeadersDsl { referrerPolicy?.also { headers.referrerPolicy(referrerPolicy) } + crossOriginOpenerPolicy?.also { + headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy) + } + crossOriginEmbedderPolicy?.also { + headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy) + } + crossOriginResourcePolicy?.also { + headers.crossOriginResourcePolicy(crossOriginResourcePolicy) + } if (disabled) { headers.disable() } diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc index f8c3f8ab133..42b1349c0e2 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc @@ -943,7 +943,7 @@ csrf-options.attlist &= headers = ## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers. -element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & permissions-policy? & header*)} +element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & permissions-policy? & cross-origin-opener-policy? & cross-origin-embedder-policy? & cross-origin-resource-policy? & header*)} headers-options.attlist &= ## Specifies if the default headers should be disabled. Default false. attribute defaults-disabled {xsd:token}? @@ -1092,6 +1092,27 @@ content-type-options.attlist &= ## If disabled, the X-Content-Type-Options header will not be included. Default false. attribute disabled {xsd:boolean}? +cross-origin-opener-policy = + ## Adds support for Cross-Origin-Opener-Policy header + element cross-origin-opener-policy {cross-origin-opener-policy-options.attlist,empty} +cross-origin-opener-policy-options.attlist &= + ## The policies for the Cross-Origin-Opener-Policy header. + attribute policy {"unsafe-none","same-origin","same-origin-allow-popups"}? + +cross-origin-embedder-policy = + ## Adds support for Cross-Origin-Embedder-Policy header + element cross-origin-embedder-policy {cross-origin-embedder-policy-options.attlist,empty} +cross-origin-embedder-policy-options.attlist &= + ## The policies for the Cross-Origin-Embedder-Policy header. + attribute policy {"unsafe-none","require-corp"}? + +cross-origin-resource-policy = + ## Adds support for Cross-Origin-Resource-Policy header + element cross-origin-resource-policy {cross-origin-resource-policy-options.attlist,empty} +cross-origin-resource-policy-options.attlist &= + ## The policies for the Cross-Origin-Resource-Policy header. + attribute policy {"cross-origin","same-origin","same-site"}? + header= ## Add additional headers to the response. element header {header.attlist} diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd index 0297b1bafed..201953d2cf1 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd @@ -2768,6 +2768,9 @@ + + + @@ -3151,6 +3154,77 @@ + + + Adds support for Cross-Origin-Opener-Policy header + + + + + + + + + + The policies for the Cross-Origin-Opener-Policy header. + + + + + + + + + + + + + + Adds support for Cross-Origin-Embedder-Policy header + + + + + + + + + + The policies for the Cross-Origin-Embedder-Policy header. + + + + + + + + + + + + + Adds support for Cross-Origin-Resource-Policy header + + + + + + + + + + The policies for the Cross-Origin-Resource-Policy header. + + + + + + + + + + + Add additional headers to the response. diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java index cc64f4b92a9..2b2f2a0b55c 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,11 +26,16 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter; +import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter; import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode; import org.springframework.test.web.servlet.MockMvc; @@ -52,6 +57,7 @@ * @author EddĂș MelĂ©ndez * @author Vedran Pavic * @author Eleftheria Stein + * @author Marcus Da Coregio */ @ExtendWith(SpringTestContextExtension.class) public class HeadersConfigurerTests { @@ -514,6 +520,30 @@ public void getWhenHstsConfiguredWithPreloadInLambdaThenStrictTransportSecurityH assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); } + @Test + public void getWhenCustomCrossOriginPoliciesInLambdaThenCrossOriginPolicyHeadersWithCustomValuesInResponse() + throws Exception { + this.spring.register(CrossOriginCustomPoliciesInLambdaConfig.class).autowire(); + MvcResult mvcResult = this.mvc.perform(get("/")) + .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin")) + .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp")) + .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")).andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, + HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY); + } + + @Test + public void getWhenCustomCrossOriginPoliciesThenCrossOriginPolicyHeadersWithCustomValuesInResponse() + throws Exception { + this.spring.register(CrossOriginCustomPoliciesConfig.class).autowire(); + MvcResult mvcResult = this.mvc.perform(get("/")) + .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin")) + .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp")) + .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")).andReturn(); + assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, + HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY); + } + @EnableWebSecurity static class HeadersConfig extends WebSecurityConfigurerAdapter { @@ -1146,4 +1176,50 @@ protected void configure(HttpSecurity http) throws Exception { } + @EnableWebSecurity + static class CrossOriginCustomPoliciesInLambdaConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http.headers((headers) -> headers + .defaultsDisabled() + .crossOriginOpenerPolicy((policy) -> policy + .policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN) + ) + .crossOriginEmbedderPolicy((policy) -> policy + .policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) + ) + .crossOriginResourcePolicy((policy) -> policy + .policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN) + ) + ); + // @formatter:on + return http.build(); + } + + } + + @EnableWebSecurity + static class CrossOriginCustomPoliciesConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http.headers() + .defaultsDisabled() + .crossOriginOpenerPolicy() + .policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN) + .and() + .crossOriginEmbedderPolicy() + .policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) + .and() + .crossOriginResourcePolicy() + .policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN); + // @formatter:on + return http.build(); + } + + } + } diff --git a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java index c399b1994ab..088bd5334d8 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,7 @@ * @author Tim Ysewyn * @author Josh Cummings * @author Rafiullah Hamedy + * @author Marcus Da Coregio */ @ExtendWith(SpringTestContextExtension.class) public class HttpHeadersConfigTests { @@ -733,6 +734,53 @@ public void requestWhenReferrerPolicyConfiguredWithSameOriginThenRespondsWithSam // @formatter:on } + @Test + public void requestWhenCrossOriginOpenerPolicyWithSameOriginAllowPopupsThenRespondsWithSameOriginAllowPopups() + throws Exception { + this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginOpenerPolicy")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(excludesDefaults()) + .andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin-allow-popups")); + // @formatter:on + } + + @Test + public void requestWhenCrossOriginEmbedderPolicyWithRequireCorpThenRespondsWithRequireCorp() throws Exception { + this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginEmbedderPolicy")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(excludesDefaults()) + .andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp")); + // @formatter:on + } + + @Test + public void requestWhenCrossOriginResourcePolicyWithSameOriginThenRespondsWithSameOrigin() throws Exception { + this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginResourcePolicy")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(excludesDefaults()) + .andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin")); + // @formatter:on + } + + @Test + public void requestWhenCrossOriginPoliciesRespondsCrossOriginPolicies() throws Exception { + this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginPolicies")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(excludesDefaults()) + .andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin")) + .andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp")) + .andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin")); + // @formatter:on + } + private static ResultMatcher includesDefaults() { return includes(defaultHeaders); } diff --git a/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java index a3167f2d140..f4b85f45bad 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,9 @@ import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter; +import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter; +import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter; +import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy; @@ -48,6 +51,7 @@ * @author Rob Winch * @author Vedran Pavic * @author Ankur Pathak + * @author Marcus Da Coregio * @since 5.0 */ public class HeaderSpecTests { @@ -406,6 +410,53 @@ public void headersWhenCustomHeadersWriter() { assertHeaders(); } + @Test + public void headersWhenCrossOriginPoliciesCustomEnabledThenCustomCrossOriginPoliciesWritten() { + this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, + CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS + .getPolicy()); + this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, + CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy()); + this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, + CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy()); + // @formatter:off + this.http.headers() + .crossOriginOpenerPolicy() + .policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS) + .and() + .crossOriginEmbedderPolicy() + .policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) + .and() + .crossOriginResourcePolicy() + .policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN); + // @formatter:on + assertHeaders(); + } + + @Test + public void headersWhenCrossOriginPoliciesCustomEnabledInLambdaThenCustomCrossOriginPoliciesWritten() { + this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, + CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS + .getPolicy()); + this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, + CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy()); + this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, + CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy()); + // @formatter:off + this.http.headers() + .crossOriginOpenerPolicy((policy) -> policy + .policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS) + ) + .crossOriginEmbedderPolicy((policy) -> policy + .policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) + ) + .crossOriginResourcePolicy((policy) -> policy + .policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN) + ); + // @formatter:on + assertHeaders(); + } + private void expectHeaderNamesNotPresent(String... headerNames) { for (String headerName : headerNames) { this.expectedHeaders.remove(headerName); diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/HeadersDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/HeadersDslTests.kt index a064cc4323f..c21f1b52ed0 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/HeadersDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/HeadersDslTests.kt @@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.web import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean import org.springframework.http.HttpHeaders import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt index 3c404e28075..c68de3b4e7b 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt @@ -28,6 +28,9 @@ import org.springframework.security.config.test.SpringTestContextExtension import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter +import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter +import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter +import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter @@ -133,4 +136,60 @@ class ServerHeadersDslTests { } } } + + @Test + fun `request when no cross-origin policies configured then does not write cross-origin policies headers in response`() { + this.spring.register(CrossOriginPoliciesConfig::class.java).autowire() + + this.client.get() + .uri("/") + .exchange() + .expectHeader().doesNotExist("Cross-Origin-Opener-Policy") + .expectHeader().doesNotExist("Cross-Origin-Embedder-Policy") + .expectHeader().doesNotExist("Cross-Origin-Resource-Policy") + } + + @EnableWebFluxSecurity + @EnableWebFlux + open class CrossOriginPoliciesConfig { + @Bean + open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + return http { + headers { } + } + } + } + + @Test + fun `request when cross-origin custom policies configured then cross-origin custom policies headers in response`() { + this.spring.register(CrossOriginPoliciesCustomConfig::class.java).autowire() + + this.client.get() + .uri("/") + .exchange() + .expectHeader().valueEquals("Cross-Origin-Opener-Policy", "same-origin") + .expectHeader().valueEquals("Cross-Origin-Embedder-Policy", "require-corp") + .expectHeader().valueEquals("Cross-Origin-Resource-Policy", "same-origin") + } + + @EnableWebFluxSecurity + @EnableWebFlux + open class CrossOriginPoliciesCustomConfig { + @Bean + open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + return http { + headers { + crossOriginOpenerPolicy { + policy = CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN + } + crossOriginEmbedderPolicy { + policy = CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP + } + crossOriginResourcePolicy { + policy = CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN + } + } + } + } + } } diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginEmbedderPolicy.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginEmbedderPolicy.xml new file mode 100644 index 00000000000..cfa473c0d55 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginEmbedderPolicy.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginOpenerPolicy.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginOpenerPolicy.xml new file mode 100644 index 00000000000..1e688e556be --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginOpenerPolicy.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginPolicies.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginPolicies.xml new file mode 100644 index 00000000000..d667ebc5e95 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginPolicies.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginResourcePolicy.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginResourcePolicy.xml new file mode 100644 index 00000000000..667933f8d6b --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginResourcePolicy.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/pages/features/exploits/headers.adoc b/docs/modules/ROOT/pages/features/exploits/headers.adoc index be19d3028cb..e142718275d 100644 --- a/docs/modules/ROOT/pages/features/exploits/headers.adoc +++ b/docs/modules/ROOT/pages/features/exploits/headers.adoc @@ -378,6 +378,26 @@ Clear-Site-Data: "cache", "cookies", "storage", "executionContexts" This is a nice clean-up action to perform on logout. +[[headers-cross-origin-policies]] +== Cross-Origin Policies + +[NOTE] +==== +Refer to the relevant sections to see how to configure for both <> and <> based applications. +==== + +Spring Security provides support for some important Cross-Origin Policies headers. +Those headers are: + +* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy[`Cross-Origin-Opener-Policy`] +* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy[`Cross-Origin-Embedder-Policy`] +* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy[`Cross-Origin-Resource-Policy`] + +`Cross-Origin-Opener-Policy` (COOP) allows a top-level document to break the association between its window and any others in the browsing context group (e.g., between a popup and its opener), preventing any direct DOM access between them. + +Enabling `Cross-Origin-Embedder-Policy` (COEP) prevents a document from loading any non-same-origin resources which don't explicitly grant the document permission to be loaded. + +The `Cross-Origin-Resource-Policy` (CORP) header allows you to control the set of origins that are empowered to include a resource. It is a robust defense against attacks like https://meltdownattack.com[Spectre], as it allows browsers to block a given response before it enters an attacker's process. [[headers-custom]] == Custom Headers diff --git a/docs/modules/ROOT/pages/reactive/exploits/headers.adoc b/docs/modules/ROOT/pages/reactive/exploits/headers.adoc index 9b61d12b3aa..30b4779fd92 100644 --- a/docs/modules/ROOT/pages/reactive/exploits/headers.adoc +++ b/docs/modules/ROOT/pages/reactive/exploits/headers.adoc @@ -578,3 +578,65 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } ---- ==== + +[[webflux-headers-cross-origin-policies]] +== Cross-Origin Policies + +Spring Security provides built-in support for adding some Cross-Origin policies headers, those headers are: + +[source] +---- +Cross-Origin-Opener-Policy +Cross-Origin-Embedder-Policy +Cross-Origin-Resource-Policy +---- + +Spring Security does not add <> headers by default. +The headers can be added with the following configuration: + +.Cross-Origin Policies +==== +.Java +[source,java,role="primary"] +---- +@EnableWebFluxSecurity +@EnableWebFlux +public class WebSecurityConfig { + + @Bean + SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) { + http.headers((headers) -> headers + .crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN) + .crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP) + .crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN)); + return http.build(); + } +} +---- +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableWebFluxSecurity +@EnableWebFlux +open class CrossOriginPoliciesCustomConfig { + @Bean + open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + return http { + headers { + crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN) + crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP) + crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN) + } + } + } +} +---- +==== + +This configuration will write the headers with the values provided: +[source] +---- +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Resource-Policy: same-origin +---- diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 8d8e454db75..eb81c2734c2 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -238,6 +238,9 @@ This allows HTTPS websites to resist impersonation by attackers using mis-issued https://www.w3.org/TR/CSP2/[Content Security Policy (CSP)] is a mechanism that web applications can leverage to mitigate content injection vulnerabilities, such as cross-site scripting (XSS). ** `Referrer-Policy` - Can be set using the <> element, https://www.w3.org/TR/referrer-policy/[Referrer-Policy] is a mechanism that web applications can leverage to manage the referrer field, which contains the last page the user was on. ** `Feature-Policy` - Can be set using the <> element, https://wicg.github.io/feature-policy/[Feature-Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser. +** `Cross-Origin-Opener-Policy` - Can be set using the <> element, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy[Cross-Origin-Opener-Policy] is a mechanism that allows you to ensure a top-level document does not share a browsing context group with cross-origin documents. +** `Cross-Origin-Embedder-Policy` - Can be set using the <> element, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy[Cross-Origin-Embedder-Policy] is a mechanism that prevents a document from loading any cross-origin resources that don't explicitly grant the document permission. +** `Cross-Origin-Resource-Policy` - Can be set using the <> element, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy[Cross-Origin-Resource-Policy] is a mechanism that conveys a desire that the browser blocks no-cors cross-origin/cross-site requests to the given resource. [[nsa-headers-attributes]] === Attributes @@ -269,6 +272,9 @@ The default is false (the headers are enabled). * <> * <> * <> +* <> +* <> +* <> * <> * <> * <> @@ -584,6 +590,66 @@ Default false. +[[nsa-cross-origin-embedder-policy]] +==== +When enabled adds the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy[Cross-Origin-Embedder-Policy] header to the response. + + +[[nsa-cross-origin-embedder-policy-attributes]] +===== Attributes + +[[nsa-cross-origin-embedder-policy-policy]] +* **policy** +The policy for the `Cross-Origin-Embedder-Policy` header. + +[[nsa-cross-origin-embedder-policy-parents]] +===== Parent Elements of + + +* <> + + + +[[nsa-cross-origin-opener-policy]] +==== +When enabled adds the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy[Cross-Origin-Opener-Policy] header to the response. + + +[[nsa-cross-origin-opener-policy-attributes]] +===== Attributes + +[[nsa-cross-origin-opener-policy-policy]] +* **policy** +The policy for the `Cross-Origin-Opener-Policy` header. + +[[nsa-cross-origin-opener-policy-parents]] +===== Parent Elements of + + +* <> + + + +[[nsa-cross-origin-resource-policy]] +==== +When enabled adds the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy[Cross-Origin-Resource-Policy] header to the response. + + +[[nsa-cross-origin-resource-policy-attributes]] +===== Attributes + +[[nsa-cross-origin-resource-policy-policy]] +* **policy** +The policy for the `Cross-Origin-Resource-Policy` header. + +[[nsa-cross-origin-resource-policy-parents]] +===== Parent Elements of + + +* <> + + + [[nsa-header]] ==

Add additional headers to the response, both the name and value need to be specified. diff --git a/docs/modules/ROOT/pages/servlet/exploits/headers.adoc b/docs/modules/ROOT/pages/servlet/exploits/headers.adoc index 535f3e976bb..de8171d2585 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/headers.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/headers.adoc @@ -938,6 +938,67 @@ class SecurityConfig : WebSecurityConfigurerAdapter() { ---- ==== +[[servlet-headers-cross-origin-policies]] +== Cross-Origin Policies + +Spring Security provides built-in support for adding some Cross-Origin policies headers, those headers are: + +[source] +---- +Cross-Origin-Opener-Policy +Cross-Origin-Embedder-Policy +Cross-Origin-Resource-Policy +---- + +Spring Security does not add <> headers by default. +The headers can be added with the following configuration: + +.Cross-Origin Policies +==== +.Java +[source,java,role="primary"] +---- +@EnableWebSecurity +public class WebSecurityConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) { + http.headers((headers) -> headers + .crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN) + .crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP) + .crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN))); + return http.build(); + } +} +---- +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableWebSecurity +open class CrossOriginPoliciesConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + headers { + crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN) + crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP) + crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN) + } + } + return http.build() + } +} +---- +==== + +This configuration will write the headers with the values provided: +[source] +---- +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Resource-Policy: same-origin +---- + [[servlet-headers-custom]] == Custom Headers Spring Security has mechanisms to make it convenient to add the more common security headers to your application. diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java new file mode 100644 index 00000000000..5ec60c051ed --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.header.writers; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.web.header.HeaderWriter; +import org.springframework.util.Assert; + +/** + * Inserts Cross-Origin-Embedder-Policy header. + * + * @author Marcus Da Coregio + * @since 5.7 + * @see + * Cross-Origin-Embedder-Policy + */ +public final class CrossOriginEmbedderPolicyHeaderWriter implements HeaderWriter { + + private static final String EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy"; + + private CrossOriginEmbedderPolicy policy; + + /** + * Sets the {@link CrossOriginEmbedderPolicy} value to be used in the + * {@code Cross-Origin-Embedder-Policy} header + * @param embedderPolicy the {@link CrossOriginEmbedderPolicy} to use + */ + public void setPolicy(CrossOriginEmbedderPolicy embedderPolicy) { + Assert.notNull(embedderPolicy, "embedderPolicy cannot be null"); + this.policy = embedderPolicy; + } + + @Override + public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { + if (this.policy != null && !response.containsHeader(EMBEDDER_POLICY)) { + response.addHeader(EMBEDDER_POLICY, this.policy.getPolicy()); + } + } + + public enum CrossOriginEmbedderPolicy { + + UNSAFE_NONE("unsafe-none"), + + REQUIRE_CORP("require-corp"); + + private final String policy; + + CrossOriginEmbedderPolicy(String policy) { + this.policy = policy; + } + + public String getPolicy() { + return this.policy; + } + + public static CrossOriginEmbedderPolicy from(String embedderPolicy) { + for (CrossOriginEmbedderPolicy policy : values()) { + if (policy.getPolicy().equals(embedderPolicy)) { + return policy; + } + } + return null; + } + + } + +} diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java new file mode 100644 index 00000000000..182a62c414b --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.header.writers; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.web.header.HeaderWriter; +import org.springframework.util.Assert; + +/** + * Inserts the Cross-Origin-Opener-Policy header + * + * @author Marcus Da Coregio + * @since 5.7 + * @see + * Cross-Origin-Opener-Policy + */ +public final class CrossOriginOpenerPolicyHeaderWriter implements HeaderWriter { + + private static final String OPENER_POLICY = "Cross-Origin-Opener-Policy"; + + private CrossOriginOpenerPolicy policy; + + /** + * Sets the {@link CrossOriginOpenerPolicy} value to be used in the + * {@code Cross-Origin-Opener-Policy} header + * @param openerPolicy the {@link CrossOriginOpenerPolicy} to use + */ + public void setPolicy(CrossOriginOpenerPolicy openerPolicy) { + Assert.notNull(openerPolicy, "openerPolicy cannot be null"); + this.policy = openerPolicy; + } + + @Override + public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { + if (this.policy != null && !response.containsHeader(OPENER_POLICY)) { + response.addHeader(OPENER_POLICY, this.policy.getPolicy()); + } + } + + public enum CrossOriginOpenerPolicy { + + UNSAFE_NONE("unsafe-none"), + + SAME_ORIGIN_ALLOW_POPUPS("same-origin-allow-popups"), + + SAME_ORIGIN("same-origin"); + + private final String policy; + + CrossOriginOpenerPolicy(String policy) { + this.policy = policy; + } + + public String getPolicy() { + return this.policy; + } + + public static CrossOriginOpenerPolicy from(String openerPolicy) { + for (CrossOriginOpenerPolicy policy : values()) { + if (policy.getPolicy().equals(openerPolicy)) { + return policy; + } + } + return null; + } + + } + +} diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java new file mode 100644 index 00000000000..d454ce780ab --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.header.writers; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.web.header.HeaderWriter; +import org.springframework.util.Assert; + +/** + * Inserts Cross-Origin-Resource-Policy header + * + * @author Marcus Da Coregio + * @since 5.7 + * @see + * Cross-Origin-Resource-Policy + */ +public final class CrossOriginResourcePolicyHeaderWriter implements HeaderWriter { + + private static final String RESOURCE_POLICY = "Cross-Origin-Resource-Policy"; + + private CrossOriginResourcePolicy policy; + + /** + * Sets the {@link CrossOriginResourcePolicy} value to be used in the + * {@code Cross-Origin-Resource-Policy} header + * @param resourcePolicy the {@link CrossOriginResourcePolicy} to use + */ + public void setPolicy(CrossOriginResourcePolicy resourcePolicy) { + Assert.notNull(resourcePolicy, "resourcePolicy cannot be null"); + this.policy = resourcePolicy; + } + + @Override + public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { + if (this.policy != null && !response.containsHeader(RESOURCE_POLICY)) { + response.addHeader(RESOURCE_POLICY, this.policy.getPolicy()); + } + } + + public enum CrossOriginResourcePolicy { + + SAME_SITE("same-site"), + + SAME_ORIGIN("same-origin"), + + CROSS_ORIGIN("cross-origin"); + + private final String policy; + + CrossOriginResourcePolicy(String policy) { + this.policy = policy; + } + + public String getPolicy() { + return this.policy; + } + + public static CrossOriginResourcePolicy from(String resourcePolicy) { + for (CrossOriginResourcePolicy policy : values()) { + if (policy.getPolicy().equals(resourcePolicy)) { + return policy; + } + } + return null; + } + + } + +} diff --git a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java new file mode 100644 index 00000000000..17446845dde --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.server.header; + +import reactor.core.publisher.Mono; + +import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebExchange; + +/** + * Inserts Cross-Origin-Embedder-Policy headers. + * + * @author Marcus Da Coregio + * @since 5.7 + * @see + * Cross-Origin-Embedder-Policy + */ +public final class CrossOriginEmbedderPolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter { + + public static final String EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy"; + + private ServerHttpHeadersWriter delegate; + + /** + * Sets the {@link CrossOriginEmbedderPolicy} value to be used in the + * {@code Cross-Origin-Embedder-Policy} header + * @param embedderPolicy the {@link CrossOriginEmbedderPolicy} to use + */ + public void setPolicy(CrossOriginEmbedderPolicy embedderPolicy) { + Assert.notNull(embedderPolicy, "embedderPolicy cannot be null"); + this.delegate = createDelegate(embedderPolicy); + } + + @Override + public Mono writeHttpHeaders(ServerWebExchange exchange) { + return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty(); + } + + private static ServerHttpHeadersWriter createDelegate(CrossOriginEmbedderPolicy embedderPolicy) { + StaticServerHttpHeadersWriter.Builder builder = StaticServerHttpHeadersWriter.builder(); + builder.header(EMBEDDER_POLICY, embedderPolicy.getPolicy()); + return builder.build(); + } + + public enum CrossOriginEmbedderPolicy { + + UNSAFE_NONE("unsafe-none"), + + REQUIRE_CORP("require-corp"); + + private final String policy; + + CrossOriginEmbedderPolicy(String policy) { + this.policy = policy; + } + + public String getPolicy() { + return this.policy; + } + + } + +} diff --git a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java new file mode 100644 index 00000000000..d02add2320e --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.server.header; + +import reactor.core.publisher.Mono; + +import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebExchange; + +/** + * Inserts Cross-Origin-Opener-Policy header. + * + * @author Marcus Da Coregio + * @since 5.7 + * @see + * Cross-Origin-Opener-Policy + */ +public final class CrossOriginOpenerPolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter { + + public static final String OPENER_POLICY = "Cross-Origin-Opener-Policy"; + + private ServerHttpHeadersWriter delegate; + + /** + * Sets the {@link CrossOriginOpenerPolicy} value to be used in the + * {@code Cross-Origin-Opener-Policy} header + * @param openerPolicy the {@link CrossOriginOpenerPolicy} to use + */ + public void setPolicy(CrossOriginOpenerPolicy openerPolicy) { + Assert.notNull(openerPolicy, "openerPolicy cannot be null"); + this.delegate = createDelegate(openerPolicy); + } + + @Override + public Mono writeHttpHeaders(ServerWebExchange exchange) { + return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty(); + } + + private static ServerHttpHeadersWriter createDelegate(CrossOriginOpenerPolicy openerPolicy) { + StaticServerHttpHeadersWriter.Builder builder = StaticServerHttpHeadersWriter.builder(); + builder.header(OPENER_POLICY, openerPolicy.getPolicy()); + return builder.build(); + } + + public enum CrossOriginOpenerPolicy { + + UNSAFE_NONE("unsafe-none"), + + SAME_ORIGIN_ALLOW_POPUPS("same-origin-allow-popups"), + + SAME_ORIGIN("same-origin"); + + private final String policy; + + CrossOriginOpenerPolicy(String policy) { + this.policy = policy; + } + + public String getPolicy() { + return this.policy; + } + + } + +} diff --git a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java new file mode 100644 index 00000000000..dff25749441 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.server.header; + +import reactor.core.publisher.Mono; + +import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebExchange; + +/** + * Inserts Cross-Origin-Resource-Policy headers. + * + * @author Marcus Da Coregio + * @since 5.7 + * @see + * Cross-Origin-Resource-Policy + */ +public final class CrossOriginResourcePolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter { + + public static final String RESOURCE_POLICY = "Cross-Origin-Resource-Policy"; + + private ServerHttpHeadersWriter delegate; + + /** + * Sets the {@link CrossOriginResourcePolicy} value to be used in the + * {@code Cross-Origin-Embedder-Policy} header + * @param resourcePolicy the {@link CrossOriginResourcePolicy} to use + */ + public void setPolicy(CrossOriginResourcePolicy resourcePolicy) { + Assert.notNull(resourcePolicy, "resourcePolicy cannot be null"); + this.delegate = createDelegate(resourcePolicy); + } + + @Override + public Mono writeHttpHeaders(ServerWebExchange exchange) { + return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty(); + } + + private static ServerHttpHeadersWriter createDelegate(CrossOriginResourcePolicy resourcePolicy) { + StaticServerHttpHeadersWriter.Builder builder = StaticServerHttpHeadersWriter.builder(); + builder.header(RESOURCE_POLICY, resourcePolicy.getPolicy()); + return builder.build(); + } + + public enum CrossOriginResourcePolicy { + + SAME_SITE("same-site"), + + SAME_ORIGIN("same-origin"), + + CROSS_ORIGIN("cross-origin"); + + private final String policy; + + CrossOriginResourcePolicy(String policy) { + this.policy = policy; + } + + public String getPolicy() { + return this.policy; + } + + } + +} diff --git a/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriterTests.java new file mode 100644 index 00000000000..0b90c57dea3 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriterTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.header.writers; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class CrossOriginEmbedderPolicyHeaderWriterTests { + + private static final String EMBEDDER_HEADER_NAME = "Cross-Origin-Embedder-Policy"; + + private CrossOriginEmbedderPolicyHeaderWriter writer; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + @BeforeEach + void setup() { + this.writer = new CrossOriginEmbedderPolicyHeaderWriter(); + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + } + + @Test + void setEmbedderPolicyWhenNullEmbedderPolicyThenThrowsIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null)) + .withMessage("embedderPolicy cannot be null"); + } + + @Test + void writeHeadersWhenDefaultValuesThenDontWriteHeaders() { + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeaderNames()).hasSize(0); + } + + @Test + void writeHeadersWhenResponseHeaderExistsThenDontOverride() { + this.response.addHeader(EMBEDDER_HEADER_NAME, "require-corp"); + this.writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.UNSAFE_NONE); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeader(EMBEDDER_HEADER_NAME)).isEqualTo("require-corp"); + } + + @Test + void writeHeadersWhenSetHeaderValuesThenWrites() { + this.writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeader(EMBEDDER_HEADER_NAME)).isEqualTo("require-corp"); + } + + @Test + void writeHeadersWhenSetEmbedderPolicyThenWritesEmbedderPolicy() { + this.writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.UNSAFE_NONE); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeaderNames()).hasSize(1); + assertThat(this.response.getHeader(EMBEDDER_HEADER_NAME)).isEqualTo("unsafe-none"); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriterTests.java new file mode 100644 index 00000000000..863351bb8b1 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriterTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.header.writers; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class CrossOriginOpenerPolicyHeaderWriterTests { + + private static final String OPENER_HEADER_NAME = "Cross-Origin-Opener-Policy"; + + private CrossOriginOpenerPolicyHeaderWriter writer; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + @BeforeEach + void setup() { + this.writer = new CrossOriginOpenerPolicyHeaderWriter(); + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + } + + @Test + void setOpenerPolicyWhenNullOpenerPolicyThenThrowsIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null)) + .withMessage("openerPolicy cannot be null"); + } + + @Test + void writeHeadersWhenDefaultValuesThenDontWriteHeaders() { + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeaderNames()).hasSize(0); + } + + @Test + void writeHeadersWhenResponseHeaderExistsThenDontOverride() { + this.response.addHeader(OPENER_HEADER_NAME, "same-origin"); + this.writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeader(OPENER_HEADER_NAME)).isEqualTo("same-origin"); + } + + @Test + void writeHeadersWhenSetHeaderValuesThenWrites() { + this.writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeader(OPENER_HEADER_NAME)).isEqualTo("same-origin-allow-popups"); + } + + @Test + void writeHeadersWhenSetOpenerPolicyThenWritesOpenerPolicy() { + this.writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeaderNames()).hasSize(1); + assertThat(this.response.getHeader(OPENER_HEADER_NAME)).isEqualTo("same-origin-allow-popups"); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriterTests.java b/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriterTests.java new file mode 100644 index 00000000000..14b8f04a031 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriterTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.header.writers; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class CrossOriginResourcePolicyHeaderWriterTests { + + private static final String RESOURCE_HEADER_NAME = "Cross-Origin-Resource-Policy"; + + private CrossOriginResourcePolicyHeaderWriter writer; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + @BeforeEach + void setup() { + this.writer = new CrossOriginResourcePolicyHeaderWriter(); + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + } + + @Test + void setResourcePolicyWhenNullThenThrowsIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null)) + .withMessage("resourcePolicy cannot be null"); + } + + @Test + void writeHeadersWhenDefaultValuesThenDontWriteHeaders() { + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeaderNames()).hasSize(0); + } + + @Test + void writeHeadersWhenResponseHeaderExistsThenDontOverride() { + this.response.addHeader(RESOURCE_HEADER_NAME, "same-site"); + this.writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.CROSS_ORIGIN); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeader(RESOURCE_HEADER_NAME)).isEqualTo("same-site"); + } + + @Test + void writeHeadersWhenSetHeaderValuesThenWrites() { + this.writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeader(RESOURCE_HEADER_NAME)).isEqualTo("same-origin"); + } + + @Test + void writeHeadersWhenSetResourcePolicyThenWritesResourcePolicy() { + this.writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_SITE); + this.writer.writeHeaders(this.request, this.response); + assertThat(this.response.getHeaderNames()).hasSize(1); + assertThat(this.response.getHeader(RESOURCE_HEADER_NAME)).isEqualTo("same-site"); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java new file mode 100644 index 00000000000..b4e99336fc2 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.server.header; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class CrossOriginEmbedderPolicyServerHttpHeadersWriterTests { + + private ServerWebExchange exchange; + + private CrossOriginEmbedderPolicyServerHttpHeadersWriter writer; + + @BeforeEach + void setup() { + this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")); + this.writer = new CrossOriginEmbedderPolicyServerHttpHeadersWriter(); + } + + @Test + void setEmbedderPolicyWhenNullEmbedderPolicyThenThrowsIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null)) + .withMessage("embedderPolicy cannot be null"); + } + + @Test + void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() { + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).isEmpty(); + } + + @Test + void writeHeadersWhenResponseHeaderExistsThenDontOverride() { + this.exchange.getResponse().getHeaders().add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, + "require-corp"); + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY)) + .containsOnly("require-corp"); + } + + @Test + void writeHeadersWhenSetHeaderValuesThenWrites() { + this.writer.setPolicy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP); + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY)) + .containsOnly("require-corp"); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java new file mode 100644 index 00000000000..0159665b4ef --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.server.header; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class CrossOriginOpenerPolicyServerHttpHeadersWriterTests { + + private ServerWebExchange exchange; + + private CrossOriginOpenerPolicyServerHttpHeadersWriter writer; + + @BeforeEach + void setup() { + this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")); + this.writer = new CrossOriginOpenerPolicyServerHttpHeadersWriter(); + } + + @Test + void setOpenerPolicyWhenNullOpenerPolicyThenThrowsIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null)) + .withMessage("openerPolicy cannot be null"); + } + + @Test + void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() { + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).isEmpty(); + } + + @Test + void writeHeadersWhenResponseHeaderExistsThenDontOverride() { + this.exchange.getResponse().getHeaders().add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, + "same-origin"); + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY)) + .containsOnly("same-origin"); + } + + @Test + void writeHeadersWhenSetHeaderValuesThenWrites() { + this.writer.setPolicy( + CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS); + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY)) + .containsOnly("same-origin-allow-popups"); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java new file mode 100644 index 00000000000..a3ba9a2ec9f --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.server.header; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class CrossOriginResourcePolicyServerHttpHeadersWriterTests { + + private ServerWebExchange exchange; + + private CrossOriginResourcePolicyServerHttpHeadersWriter writer; + + @BeforeEach + void setup() { + this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")); + this.writer = new CrossOriginResourcePolicyServerHttpHeadersWriter(); + } + + @Test + void setResourcePolicyWhenNullThenThrowsIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null)) + .withMessage("resourcePolicy cannot be null"); + } + + @Test + void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() { + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).isEmpty(); + } + + @Test + void writeHeadersWhenResponseHeaderExistsThenDontOverride() { + this.exchange.getResponse().getHeaders().add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, + "same-origin"); + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY)) + .containsOnly("same-origin"); + } + + @Test + void writeHeadersWhenSetHeaderValuesThenWrites() { + this.writer.setPolicy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN); + this.writer.writeHttpHeaders(this.exchange); + HttpHeaders headers = this.exchange.getResponse().getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY)) + .containsOnly("same-origin"); + } + +}