Add header based remote access security
Update the remote endpoints to use 'shared secret' authentication. Secrets are provided as Environment properties and transfered using a custom HTTP header. See gh-3082
This commit is contained in:
parent
fe4c0022d7
commit
207347e150
|
|
@ -34,6 +34,7 @@ import org.springframework.boot.developertools.remote.server.Dispatcher;
|
|||
import org.springframework.boot.developertools.remote.server.DispatcherFilter;
|
||||
import org.springframework.boot.developertools.remote.server.Handler;
|
||||
import org.springframework.boot.developertools.remote.server.HandlerMapper;
|
||||
import org.springframework.boot.developertools.remote.server.HttpHeaderAccessManager;
|
||||
import org.springframework.boot.developertools.remote.server.HttpStatusHandler;
|
||||
import org.springframework.boot.developertools.remote.server.UrlHandlerMapper;
|
||||
import org.springframework.boot.developertools.restart.server.DefaultSourceFolderUrlFilter;
|
||||
|
|
@ -56,7 +57,7 @@ import org.springframework.http.server.ServerHttpRequest;
|
|||
* @since 1.3.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "spring.developertools.remote", name = "enabled")
|
||||
@ConditionalOnProperty(prefix = "spring.developertools.remote", name = "secret")
|
||||
@ConditionalOnClass({ Filter.class, ServerHttpRequest.class })
|
||||
@EnableConfigurationProperties(DeveloperToolsProperties.class)
|
||||
public class RemoteDeveloperToolsAutoConfiguration {
|
||||
|
|
@ -67,6 +68,14 @@ public class RemoteDeveloperToolsAutoConfiguration {
|
|||
@Autowired
|
||||
private DeveloperToolsProperties properties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AccessManager remoteDeveloperToolsAccessManager() {
|
||||
RemoteDeveloperToolsProperties remoteProperties = this.properties.getRemote();
|
||||
return new HttpHeaderAccessManager(remoteProperties.getSecretHeaderName(),
|
||||
remoteProperties.getSecret());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HandlerMapper remoteDeveloperToolsHealthCheckHandlerMapper() {
|
||||
Handler handler = new HttpStatusHandler();
|
||||
|
|
@ -76,8 +85,8 @@ public class RemoteDeveloperToolsAutoConfiguration {
|
|||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DispatcherFilter remoteDeveloperToolsDispatcherFilter(
|
||||
Collection<HandlerMapper> mappers) {
|
||||
Dispatcher dispatcher = new Dispatcher(AccessManager.PERMIT_ALL, mappers);
|
||||
AccessManager accessManager, Collection<HandlerMapper> mappers) {
|
||||
Dispatcher dispatcher = new Dispatcher(accessManager, mappers);
|
||||
return new DispatcherFilter(dispatcher);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,24 @@ public class RemoteDeveloperToolsProperties {
|
|||
|
||||
public static final String DEFAULT_CONTEXT_PATH = "/.~~spring-boot!~";
|
||||
|
||||
public static final String DEFAULT_SECRET_HEADER_NAME = "X-AUTH-TOKEN";
|
||||
|
||||
/**
|
||||
* Context path used to handle the remote connection.
|
||||
*/
|
||||
private String contextPath = DEFAULT_CONTEXT_PATH;
|
||||
|
||||
/**
|
||||
* A shared secret required to establish a connection (required to enable remote
|
||||
* support).
|
||||
*/
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* HTTP header used to transfer the shared secret.
|
||||
*/
|
||||
private String secretHeaderName = DEFAULT_SECRET_HEADER_NAME;
|
||||
|
||||
private Restart restart = new Restart();
|
||||
|
||||
private Debug debug = new Debug();
|
||||
|
|
@ -45,6 +58,22 @@ public class RemoteDeveloperToolsProperties {
|
|||
this.contextPath = contextPath;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getSecretHeaderName() {
|
||||
return this.secretHeaderName;
|
||||
}
|
||||
|
||||
public void setSecretHeaderName(String secretHeaderName) {
|
||||
this.secretHeaderName = secretHeaderName;
|
||||
}
|
||||
|
||||
public Restart getRestart() {
|
||||
return this.restart;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://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.boot.developertools.remote.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequestInterceptor} to populate arbitrary HTTP headers with a value.
|
||||
* For example, it might be used to provide an X-AUTH-TOKEN and value for security
|
||||
* purposes.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class HttpHeaderInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* Creates a new {@link HttpHeaderInterceptor} instance.
|
||||
* @param name the header name to populate. Cannot be null or empty.
|
||||
* @param value the header value to populate. Cannot be null or empty.
|
||||
*/
|
||||
public HttpHeaderInterceptor(String name, String value) {
|
||||
Assert.hasLength(name, "Name must not be empty");
|
||||
Assert.hasLength(value, "Value" + " must not be empty");
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
|
||||
ClientHttpRequestExecution execution) throws IOException {
|
||||
request.getHeaders().add(this.name, this.value);
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
package org.springframework.boot.developertools.remote.client;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
|
@ -51,7 +53,10 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configuration used to connect to remote Spring Boot applications.
|
||||
|
|
@ -79,7 +84,20 @@ public class RemoteClientConfiguration {
|
|||
|
||||
@Bean
|
||||
public ClientHttpRequestFactory clientHttpRequestFactory() {
|
||||
return new SimpleClientHttpRequestFactory();
|
||||
List<ClientHttpRequestInterceptor> interceptors = Arrays
|
||||
.asList(getSecurityInterceptor());
|
||||
return new InterceptingClientHttpRequestFactory(
|
||||
new SimpleClientHttpRequestFactory(), interceptors);
|
||||
}
|
||||
|
||||
private ClientHttpRequestInterceptor getSecurityInterceptor() {
|
||||
RemoteDeveloperToolsProperties remoteProperties = this.properties.getRemote();
|
||||
String secretHeaderName = remoteProperties.getSecretHeaderName();
|
||||
String secret = remoteProperties.getSecret();
|
||||
Assert.state(secret != null,
|
||||
"The environment value 'spring.developertools.remote.secret' "
|
||||
+ "is required to secure your connection.");
|
||||
return new HttpHeaderInterceptor(secretHeaderName, secret);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://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.boot.developertools.remote.server;
|
||||
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link AccessManager} that checks for the presence of a HTTP header secret.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class HttpHeaderAccessManager implements AccessManager {
|
||||
|
||||
private final String headerName;
|
||||
|
||||
private final String expectedSecret;
|
||||
|
||||
public HttpHeaderAccessManager(String headerName, String expectedSecret) {
|
||||
Assert.hasLength(headerName, "HeaderName must not be empty");
|
||||
Assert.hasLength(expectedSecret, "ExpectedSecret must not be empty");
|
||||
this.headerName = headerName;
|
||||
this.expectedSecret = expectedSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(ServerHttpRequest request) {
|
||||
String providedSecret = request.getHeaders().getFirst(this.headerName);
|
||||
return this.expectedSecret.equals(providedSecret);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -60,6 +60,8 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
|
||||
private static final String DEFAULT_CONTEXT_PATH = RemoteDeveloperToolsProperties.DEFAULT_CONTEXT_PATH;
|
||||
|
||||
private static final String DEFAULT_SECRET_HEADER_NAME = RemoteDeveloperToolsProperties.DEFAULT_SECRET_HEADER_NAME;
|
||||
|
||||
@Rule
|
||||
public MockRestarter mockRestarter = new MockRestarter();
|
||||
|
||||
|
|
@ -88,27 +90,55 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledIfRemoteSecretIsMissing() throws Exception {
|
||||
loadContext("a:b");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(DispatcherFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresUnmappedUrl() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true");
|
||||
loadContext("spring.developertools.remote.secret:supersecret");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI("/restart");
|
||||
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertRestartInvoked(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresIfMissingSecretFromRequest() throws Exception {
|
||||
loadContext("spring.developertools.remote.secret:supersecret");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertRestartInvoked(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresInvalidSecretInRequest() throws Exception {
|
||||
loadContext("spring.developertools.remote.secret:supersecret");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart");
|
||||
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "invalid");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertRestartInvoked(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeRestartWithDefaultSetup() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true");
|
||||
loadContext("spring.developertools.remote.secret:supersecret");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/restart");
|
||||
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertRestartInvoked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableRestart() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true",
|
||||
loadContext("spring.developertools.remote.secret:supersecret",
|
||||
"spring.developertools.remote.restart.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean("remoteRestartHanderMapper");
|
||||
|
|
@ -116,16 +146,28 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
|
||||
@Test
|
||||
public void invokeTunnelWithDefaultSetup() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true");
|
||||
loadContext("spring.developertools.remote.secret:supersecret");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/debug");
|
||||
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertTunnelInvoked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeTunnelWithCustomHeaderName() throws Exception {
|
||||
loadContext("spring.developertools.remote.secret:supersecret",
|
||||
"spring.developertools.remote.secretHeaderName:customheader");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/debug");
|
||||
this.request.addHeader("customheader", "supersecret");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertTunnelInvoked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableRemoteDebug() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true",
|
||||
loadContext("spring.developertools.remote.secret:supersecret",
|
||||
"spring.developertools.remote.debug.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean("remoteDebugHanderMapper");
|
||||
|
|
@ -133,9 +175,10 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
|
||||
@Test
|
||||
public void developerToolsHealthReturns200() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true");
|
||||
loadContext("spring.developertools.remote.secret:supersecret");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH);
|
||||
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
|
||||
this.response.setStatus(500);
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus(), equalTo(200));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://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.boot.developertools.remote.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpHeaderInterceptor}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HttpHeaderInterceptorTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private String name;
|
||||
|
||||
private String value;
|
||||
|
||||
private HttpHeaderInterceptor interceptor;
|
||||
|
||||
private HttpRequest request;
|
||||
|
||||
private byte[] body;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestExecution execution;
|
||||
|
||||
@Mock
|
||||
private ClientHttpResponse response;
|
||||
|
||||
private MockHttpServletRequest httpRequest;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
this.body = new byte[] {};
|
||||
this.httpRequest = new MockHttpServletRequest();
|
||||
this.request = new ServletServerHttpRequest(this.httpRequest);
|
||||
this.name = "X-AUTH-TOKEN";
|
||||
this.value = "secret";
|
||||
given(this.execution.execute(this.request, this.body)).willReturn(this.response);
|
||||
this.interceptor = new HttpHeaderInterceptor(this.name, this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullHeaderName() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Name must not be empty");
|
||||
new HttpHeaderInterceptor(null, this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorEmptyHeaderName() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Name must not be empty");
|
||||
new HttpHeaderInterceptor("", this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullHeaderValue() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Value must not be empty");
|
||||
new HttpHeaderInterceptor(this.name, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorEmptyHeaderValue() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Value must not be empty");
|
||||
new HttpHeaderInterceptor(this.name, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intercept() throws IOException {
|
||||
ClientHttpResponse result = this.interceptor.intercept(this.request, this.body,
|
||||
this.execution);
|
||||
assertThat(this.request.getHeaders().getFirst(this.name), equalTo(this.value));
|
||||
assertThat(result, equalTo(this.response));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import org.junit.After;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
|
||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||
|
|
@ -92,16 +93,23 @@ public class RemoteClientConfigurationTests {
|
|||
|
||||
@Test
|
||||
public void warnIfNotHttps() throws Exception {
|
||||
configureWithRemoteUrl("http://localhost");
|
||||
configure("http://localhost", true);
|
||||
assertThat(this.output.toString(), containsString("is insecure"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesntWarnIfUsingHttps() throws Exception {
|
||||
configureWithRemoteUrl("https://localhost");
|
||||
configure("https://localhost", true);
|
||||
assertThat(this.output.toString(), not(containsString("is insecure")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failIfNoSecret() throws Exception {
|
||||
this.thrown.expect(BeanCreationException.class);
|
||||
this.thrown.expectMessage("required to secure your connection");
|
||||
configure("http://localhost", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void liveReloadOnClassPathChanged() throws Exception {
|
||||
configure();
|
||||
|
|
@ -138,10 +146,10 @@ public class RemoteClientConfigurationTests {
|
|||
}
|
||||
|
||||
private void configure(String... pairs) {
|
||||
configureWithRemoteUrl("http://localhost", pairs);
|
||||
configure("http://localhost", true, pairs);
|
||||
}
|
||||
|
||||
private void configureWithRemoteUrl(String remoteUrl, String... pairs) {
|
||||
private void configure(String remoteUrl, boolean setSecret, String... pairs) {
|
||||
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||
new RestartScopeInitializer().initialize(this.context);
|
||||
this.context.register(Config.class, RemoteClientConfiguration.class);
|
||||
|
|
@ -149,6 +157,10 @@ public class RemoteClientConfigurationTests {
|
|||
+ RemoteClientConfigurationTests.remotePort;
|
||||
EnvironmentTestUtils.addEnvironment(this.context, remoteUrlProperty);
|
||||
EnvironmentTestUtils.addEnvironment(this.context, pairs);
|
||||
if (setSecret) {
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.developertools.remote.secret:secret");
|
||||
}
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://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.boot.developertools.remote.server;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpHeaderAccessManager}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpHeaderAccessManagerTests {
|
||||
|
||||
private static final String HEADER = "X-AUTH_TOKEN";
|
||||
|
||||
private static final String SECRET = "password";
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private ServerHttpRequest serverRequest;
|
||||
|
||||
private HttpHeaderAccessManager manager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest("GET", "/");
|
||||
this.serverRequest = new ServletServerHttpRequest(this.request);
|
||||
this.manager = new HttpHeaderAccessManager(HEADER, SECRET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerNameMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("HeaderName must not be empty");
|
||||
new HttpHeaderAccessManager(null, SECRET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerNameMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("HeaderName must not be empty");
|
||||
new HttpHeaderAccessManager("", SECRET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expectedSecretMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ExpectedSecret must not be empty");
|
||||
new HttpHeaderAccessManager(HEADER, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expectedSecretMustNotBeEmpty() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ExpectedSecret must not be empty");
|
||||
new HttpHeaderAccessManager(HEADER, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowsMatching() throws Exception {
|
||||
this.request.addHeader(HEADER, SECRET);
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disallowsWrongSecret() throws Exception {
|
||||
this.request.addHeader(HEADER, "wrong");
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disallowsNoSecret() throws Exception {
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disallowsWrongHeader() throws Exception {
|
||||
this.request.addHeader("X-WRONG", SECRET);
|
||||
assertThat(this.manager.isAllowed(this.serverRequest), equalTo(false));
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue