21 changed files with 1015 additions and 3 deletions
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
/* |
||||
* Copyright 2002-2020 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.web.server.header.ReferrerPolicyServerHttpHeadersWriter |
||||
|
||||
/** |
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] permissions policy header using |
||||
* idiomatic Kotlin code. |
||||
* |
||||
* @author Christophe Gilles |
||||
* @since 5.5 |
||||
* @property policy the policy to be used in the response header. |
||||
*/ |
||||
@ServerSecurityMarker |
||||
class ServerPermissionsPolicyDsl { |
||||
var policy: String? = null |
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit { |
||||
return { permissionsPolicy -> |
||||
policy?.also { |
||||
permissionsPolicy.policy(policy) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2002-2020 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.servlet.headers |
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity |
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer |
||||
|
||||
/** |
||||
* A Kotlin DSL to configure the [HttpSecurity] permissions policy header using |
||||
* idiomatic Kotlin code. |
||||
* |
||||
* @author Christophe Gilles |
||||
* @since 5.5 |
||||
* @property policy the policy to be used in the response header. |
||||
*/ |
||||
@HeadersSecurityMarker |
||||
class PermissionsPolicyDsl { |
||||
var policy: String? = null |
||||
|
||||
internal fun get(): (HeadersConfigurer<HttpSecurity>.PermissionsPolicyConfig) -> Unit { |
||||
return { permissionsPolicy -> |
||||
policy?.also { |
||||
permissionsPolicy.policy(policy) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
/* |
||||
* Copyright 2002-2020 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.junit.Rule |
||||
import org.junit.Test |
||||
import org.springframework.beans.factory.annotation.Autowired |
||||
import org.springframework.context.ApplicationContext |
||||
import org.springframework.context.annotation.Bean |
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity |
||||
import org.springframework.security.config.test.SpringTestRule |
||||
import org.springframework.security.web.server.SecurityWebFilterChain |
||||
import org.springframework.test.web.reactive.server.WebTestClient |
||||
import org.springframework.web.reactive.config.EnableWebFlux |
||||
|
||||
/** |
||||
* Tests for [ServerPermissionsPolicyDsl] |
||||
* |
||||
* @author Christophe Gilles |
||||
*/ |
||||
class ServerPermissionsPolicyDslTests { |
||||
@Rule |
||||
@JvmField |
||||
val spring = SpringTestRule() |
||||
|
||||
private lateinit var client: WebTestClient |
||||
|
||||
@Autowired |
||||
fun setup(context: ApplicationContext) { |
||||
this.client = WebTestClient |
||||
.bindToApplicationContext(context) |
||||
.configureClient() |
||||
.build() |
||||
} |
||||
|
||||
@Test |
||||
fun `request when permissions policy configured then permissions policy header in response`() { |
||||
this.spring.register(PermissionsPolicyConfig::class.java).autowire() |
||||
|
||||
this.client.get() |
||||
.uri("/") |
||||
.exchange() |
||||
.expectHeader().doesNotExist("Permissions-Policy") |
||||
} |
||||
|
||||
@EnableWebFluxSecurity |
||||
@EnableWebFlux |
||||
open class PermissionsPolicyConfig { |
||||
@Bean |
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
||||
return http { |
||||
headers { |
||||
permissionsPolicy { } |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `request when custom policy configured then custom policy in response header`() { |
||||
this.spring.register(CustomPolicyConfig::class.java).autowire() |
||||
|
||||
this.client.get() |
||||
.uri("/") |
||||
.exchange() |
||||
.expectHeader().valueEquals("Permissions-Policy", "geolocation=(self)") |
||||
} |
||||
|
||||
@EnableWebFluxSecurity |
||||
@EnableWebFlux |
||||
open class CustomPolicyConfig { |
||||
@Bean |
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
||||
return http { |
||||
headers { |
||||
permissionsPolicy { |
||||
policy = "geolocation=(self)" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!-- |
||||
~ Copyright 2002-2020 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. |
||||
--> |
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xmlns="http://www.springframework.org/schema/security" |
||||
xsi:schemaLocation=" |
||||
http://www.springframework.org/schema/security |
||||
https://www.springframework.org/schema/security/spring-security.xsd |
||||
http://www.springframework.org/schema/beans |
||||
https://www.springframework.org/schema/beans/spring-beans.xsd"> |
||||
|
||||
<http auto-config="true"> |
||||
<headers defaults-disabled="true"> |
||||
<permissions-policy policy="geolocation=(self)"/> |
||||
</headers> |
||||
</http> |
||||
|
||||
<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/> |
||||
|
||||
<b:import resource="userservice.xml"/> |
||||
</b:beans> |
||||
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
/* |
||||
* Copyright 2002-2020 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; |
||||
|
||||
/** |
||||
* Provides support for |
||||
* <a href="https://w3c.github.io/webappsec-permissions-policy//">Permisisons Policy</a>. |
||||
* <p> |
||||
* Permissions Policy allows web developers to selectively enable, disable, and modify the |
||||
* behavior of certain APIs and web features in the browser. |
||||
* <p> |
||||
* A declaration of a permissions policy contains a set of security policies, each |
||||
* responsible for declaring the restrictions for a particular feature type. |
||||
* |
||||
* @author Christophe Gilles |
||||
* @since 5.5 |
||||
*/ |
||||
public final class PermissionsPolicyHeaderWriter implements HeaderWriter { |
||||
|
||||
private static final String PERMISSIONS_POLICY_HEADER = "Permissions-Policy"; |
||||
|
||||
private String policy; |
||||
|
||||
/** |
||||
* Create a new instance of {@link PermissionsPolicyHeaderWriter}. |
||||
*/ |
||||
public PermissionsPolicyHeaderWriter() { |
||||
} |
||||
|
||||
/** |
||||
* Create a new instance of {@link PermissionsPolicyHeaderWriter} with supplied |
||||
* security policy. |
||||
* @param policy the security policy |
||||
* @throws IllegalArgumentException if policy is {@code null} or empty |
||||
*/ |
||||
public PermissionsPolicyHeaderWriter(String policy) { |
||||
setPolicy(policy); |
||||
} |
||||
|
||||
/** |
||||
* Sets the policy to be used in the response header. |
||||
* @param policy a permissions policy |
||||
* @throws IllegalArgumentException if policy is null |
||||
*/ |
||||
public void setPolicy(String policy) { |
||||
Assert.hasLength(policy, "policy can not be null or empty"); |
||||
this.policy = policy; |
||||
} |
||||
|
||||
@Override |
||||
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { |
||||
if (!response.containsHeader(PERMISSIONS_POLICY_HEADER)) { |
||||
response.setHeader(PERMISSIONS_POLICY_HEADER, this.policy); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return getClass().getName() + " [policy=" + this.policy + "]"; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* |
||||
* Copyright 2002-2020 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.security.web.server.header.StaticServerHttpHeadersWriter.Builder; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
/** |
||||
* Writes the {@code Permissions-Policy} response header with configured policy |
||||
* directives. |
||||
* |
||||
* @author Christophe Gilles |
||||
* @since 5.5 |
||||
*/ |
||||
public final class PermissionsPolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter { |
||||
|
||||
public static final String PERMISSIONS_POLICY = "Permissions-Policy"; |
||||
|
||||
private ServerHttpHeadersWriter delegate; |
||||
|
||||
@Override |
||||
public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) { |
||||
return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty(); |
||||
} |
||||
|
||||
private static ServerHttpHeadersWriter createDelegate(String policyDirectives) { |
||||
Builder builder = StaticServerHttpHeadersWriter.builder(); |
||||
builder.header(PERMISSIONS_POLICY, policyDirectives); |
||||
return builder.build(); |
||||
} |
||||
|
||||
/** |
||||
* Set the policy to be used in the response header. |
||||
* @param policy the policy |
||||
* @throws IllegalArgumentException if policy is {@code null} |
||||
*/ |
||||
public void setPolicy(String policy) { |
||||
Assert.notNull(policy, "policy must not be null"); |
||||
this.delegate = createDelegate(policy); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Copyright 2002-2020 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.Before; |
||||
import org.junit.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; |
||||
|
||||
/** |
||||
* Tests for {@link PermissionsPolicyHeaderWriter}. |
||||
* |
||||
* @author Christophe Gilles |
||||
*/ |
||||
public class PermissionsPolicyHeaderWriterTests { |
||||
|
||||
private static final String DEFAULT_POLICY_DIRECTIVES = "geolocation=(self)"; |
||||
|
||||
private MockHttpServletRequest request; |
||||
|
||||
private MockHttpServletResponse response; |
||||
|
||||
private PermissionsPolicyHeaderWriter writer; |
||||
|
||||
private static final String PERMISSIONS_POLICY_HEADER = "Permissions-Policy"; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
this.request = new MockHttpServletRequest(); |
||||
this.response = new MockHttpServletResponse(); |
||||
this.writer = new PermissionsPolicyHeaderWriter(DEFAULT_POLICY_DIRECTIVES); |
||||
} |
||||
|
||||
@Test |
||||
public void writeHeadersPermissionsPolicyDefault() { |
||||
this.writer.writeHeaders(this.request, this.response); |
||||
assertThat(this.response.getHeaderNames()).hasSize(1); |
||||
assertThat(this.response.getHeader("Permissions-Policy")).isEqualTo(DEFAULT_POLICY_DIRECTIVES); |
||||
} |
||||
|
||||
@Test |
||||
public void createWriterWithNullPolicyShouldThrowException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new PermissionsPolicyHeaderWriter(null)) |
||||
.withMessage("policy can not be null or empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void createWriterWithEmptyPolicyShouldThrowException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new PermissionsPolicyHeaderWriter("")) |
||||
.withMessage("policy can not be null or empty"); |
||||
} |
||||
|
||||
@Test |
||||
public void writeHeaderOnlyIfNotPresent() { |
||||
String value = new String("value"); |
||||
this.response.setHeader(PERMISSIONS_POLICY_HEADER, value); |
||||
this.writer.writeHeaders(this.request, this.response); |
||||
assertThat(this.response.getHeader(PERMISSIONS_POLICY_HEADER)).isSameAs(value); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* Copyright 2002-2020 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.Before; |
||||
import org.junit.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; |
||||
|
||||
/** |
||||
* Tests for {@link PermissionsPolicyServerHttpHeadersWriter}. |
||||
* |
||||
* @author Christophe Gilles |
||||
*/ |
||||
public class PermissionsPolicyServerHttpHeadersWriterTests { |
||||
|
||||
private static final String DEFAULT_POLICY_DIRECTIVES = "geolocation=(self)"; |
||||
|
||||
private ServerWebExchange exchange; |
||||
|
||||
private PermissionsPolicyServerHttpHeadersWriter writer; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")); |
||||
this.writer = new PermissionsPolicyServerHttpHeadersWriter(); |
||||
} |
||||
|
||||
@Test |
||||
public void writeHeadersWhenUsingDefaultsThenDoesNotWrite() { |
||||
this.writer.writeHttpHeaders(this.exchange); |
||||
HttpHeaders headers = this.exchange.getResponse().getHeaders(); |
||||
assertThat(headers).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void writeHeadersWhenUsingPolicyThenWritesPolicy() { |
||||
this.writer.setPolicy(DEFAULT_POLICY_DIRECTIVES); |
||||
this.writer.writeHttpHeaders(this.exchange); |
||||
HttpHeaders headers = this.exchange.getResponse().getHeaders(); |
||||
assertThat(headers).hasSize(1); |
||||
assertThat(headers.get(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY)) |
||||
.containsOnly(DEFAULT_POLICY_DIRECTIVES); |
||||
} |
||||
|
||||
@Test |
||||
public void writeHeadersWhenAlreadyWrittenThenWritesHeader() { |
||||
this.writer.setPolicy(DEFAULT_POLICY_DIRECTIVES); |
||||
String headerValue = "camera=(self)"; |
||||
this.exchange.getResponse().getHeaders().set(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY, |
||||
headerValue); |
||||
this.writer.writeHttpHeaders(this.exchange); |
||||
HttpHeaders headers = this.exchange.getResponse().getHeaders(); |
||||
assertThat(headers).hasSize(1); |
||||
assertThat(headers.get(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY)).containsOnly(headerValue); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue