DATAGEODE-194 - Add support to authenticate HTTP requests when using @EnableClusterConfiguration and @EnableSecurity.

This commit is contained in:
John Blum
2019-05-18 20:05:00 -07:00
parent b54e3b60b5
commit ac8da3b238
5 changed files with 351 additions and 6 deletions

View File

@@ -0,0 +1,237 @@
/*
* Copyright 2018 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.data.gemfire.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.ClusterSchemaObjectInitializer;
import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.SchemaObjectContext;
import java.lang.reflect.Field;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.util.Optional;
import java.util.Properties;
import org.apache.geode.management.internal.security.ResourceConstants;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.data.gemfire.config.admin.GemfireAdminOperations;
import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate;
import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.RestTemplate;
/**
* Integration Test for {@link EnableClusterConfiguration} and {@link EnableSecurity}.
*
* @author John Blum
* @see java.net.Authenticator
* @see java.net.PasswordAuthentication
* @see java.net.URI
* @see java.util.Properties
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.context.ApplicationContextInitializer
* @see org.springframework.context.ConfigurableApplicationContext
* @see org.springframework.core.env.ConfigurableEnvironment
* @see org.springframework.core.env.PropertiesPropertySource
* @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations
* @see org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects
* @see org.springframework.http.client.ClientHttpRequestInterceptor
* @see org.springframework.http.client.InterceptingClientHttpRequestFactory
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner
* @see org.springframework.web.client.RestTemplate
* @since 2.2.0
*/
@SuppressWarnings("unused")
@RunWith(SpringRunner.class)
@ContextConfiguration(initializers = EnableClusterConfigurationWithSecurityIntegrationTests.SecurityConfigurationApplicationContextInitializer.class)
public class EnableClusterConfigurationWithSecurityIntegrationTests {
@Autowired
@Qualifier("GemFireSecurityAuthenticator")
private Authenticator authenticator;
@Autowired
@Qualifier("loggingAwareClientHttpRequestInterceptor")
private ClientHttpRequestInterceptor loggingAwareClientHttpRequestInterceptor;
@Autowired
@Qualifier("securityAwareClientHttpRequestInterceptor")
private ClientHttpRequestInterceptor securityAwareClientHttpRequestInterceptor;
@Autowired
private ClusterSchemaObjectInitializer initializer;
@SuppressWarnings("unchecked")
private <T> T resolveFieldValue(Object target, String fieldName) throws NoSuchFieldException {
Field field = ReflectionUtils.findField(target.getClass(), fieldName);
return Optional.ofNullable(field)
.map(it -> {
ReflectionUtils.makeAccessible(it);
return field;
})
.map(it -> (T) ReflectionUtils.getField(it, target))
.orElseThrow(() ->
new NoSuchFieldException(String.format("Field with name [%s] was not found on Object of type [%s]",
fieldName, target.getClass().getName())));
}
@Before
public void setup() {
assertThat(this.authenticator).isNotNull();
assertThat(this.initializer).isNotNull();
assertThat(this.loggingAwareClientHttpRequestInterceptor).isNotNull();
assertThat(this.securityAwareClientHttpRequestInterceptor).isNotNull();
}
@Test
public void authenticatorReturnsPasswordAuthenticationWithGemFireSecurityProperties() {
PasswordAuthentication passwordAuthentication =
Authenticator.requestPasswordAuthentication("localhost", null, 80,
"https", null, "https");
assertThat(passwordAuthentication).isNotNull();
assertThat(passwordAuthentication.getUserName()).isEqualTo("skeletor");
assertThat(String.valueOf(passwordAuthentication.getPassword())).isEqualTo("s3cr3t");
}
@Test
public void initializerRestTemplateIncludesClientHttpRequestInterceptors() throws Exception {
SchemaObjectContext schemaObjectContext = this.initializer.getSchemaObjectContext();
assertThat(schemaObjectContext).isNotNull();
assertThat(schemaObjectContext.<GemfireAdminOperations>getGemfireAdminOperations())
.isInstanceOf(RestHttpGemfireAdminTemplate.class);
RestHttpGemfireAdminTemplate template = schemaObjectContext.getGemfireAdminOperations();
RestTemplate restTemplate = resolveFieldValue(template, "restTemplate");
assertThat(restTemplate).isNotNull();
assertThat(restTemplate.getInterceptors()).containsExactly(this.securityAwareClientHttpRequestInterceptor,
this.loggingAwareClientHttpRequestInterceptor);
assertThat(restTemplate.getRequestFactory()).isInstanceOf(InterceptingClientHttpRequestFactory.class);
}
@Test
public void securityAwareClientHttpRequestInterceptorAppliesGemFireSecurityPropertiesToHttpHeaders()
throws Exception {
byte[] body = new byte[0];
URI uri = URI.create("https://localhost:8080/gemfire/v1");
ClientHttpRequestExecution mockClientHttpRequestExecution = mock(ClientHttpRequestExecution.class);
HttpHeaders httpHeaders = new HttpHeaders();
HttpRequest mockHttpRequest = mock(HttpRequest.class);
when(mockHttpRequest.getHeaders()).thenReturn(httpHeaders);
when(mockHttpRequest.getURI()).thenReturn(uri);
this.securityAwareClientHttpRequestInterceptor.intercept(mockHttpRequest, body, mockClientHttpRequestExecution);
assertThat(httpHeaders).containsKeys(ResourceConstants.USER_NAME, ResourceConstants.PASSWORD);
assertThat(httpHeaders.getFirst(ResourceConstants.USER_NAME)).isEqualTo("skeletor");
assertThat(httpHeaders.getFirst(ResourceConstants.PASSWORD)).isEqualTo("s3cr3t");
verify(mockClientHttpRequestExecution, times(1)).execute(eq(mockHttpRequest), eq(body));
verify(mockHttpRequest, times(1)).getHeaders();
verify(mockHttpRequest, times(1)).getURI();
}
static class SecurityConfigurationApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Properties gemfireSecurityProperties = new Properties();
gemfireSecurityProperties.setProperty("spring.data.gemfire.security.username", "skeletor");
gemfireSecurityProperties.setProperty("spring.data.gemfire.security.password", "s3cr3t");
environment.getPropertySources().
addFirst(new PropertiesPropertySource("EnableClusterConfigurationWithSecurityIntegrationTests",
gemfireSecurityProperties));
}
}
@ClientCacheApplication
@EnableSecurity
@EnableGemFireMockObjects
@EnableClusterConfiguration(useHttp = true)
static class TestConfiguration {
@Bean
BeanPostProcessor clusterSchemaObjectInitializerPostProcessor() {
return new BeanPostProcessor() {
@Nullable @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ClusterSchemaObjectInitializer) {
ClusterSchemaObjectInitializer initializer = spy((ClusterSchemaObjectInitializer) bean);
doReturn(false).when(initializer).isAutoStartup();
bean = initializer;
}
return bean;
}
};
}
}
}