diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java index 395cf123..51140d8b 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java @@ -99,6 +99,7 @@ public class ApacheShiroSecurityConfiguration extends AbstractAnnotationConfigSu * @see org.springframework.beans.factory.BeanFactory */ @Override + @SuppressWarnings("all") public void setBeanFactory(BeanFactory beanFactory) throws BeansException { super.setBeanFactory(Optional.ofNullable(beanFactory) @@ -197,10 +198,12 @@ public class ApacheShiroSecurityConfiguration extends AbstractAnnotationConfigSu protected List resolveRealms() { try { - Map realmBeans = getListableBeanFactory() - .getBeansOfType(Realm.class, false, true); + Map realmBeans = getListableBeanFactory().getBeansOfType(Realm.class, false, true); + List realms = new ArrayList<>(CollectionUtils.nullSafeMap(realmBeans).values()); - Collections.sort(realms, OrderComparator.INSTANCE); + + realms.sort(OrderComparator.INSTANCE); + return realms; } catch (Exception ignore) { @@ -278,6 +281,7 @@ public class ApacheShiroSecurityConfiguration extends AbstractAnnotationConfigSu * @inheritDoc */ @Override + @SuppressWarnings("all") public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ClassUtils.isPresent(APACHE_SHIRO_LIFECYCLE_BEAN_POST_PROCESSOR_CLASS_NAME, context.getClassLoader()); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/AutoConfiguredAuthenticationConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/AutoConfiguredAuthenticationConfiguration.java new file mode 100644 index 00000000..66b175ee --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/AutoConfiguredAuthenticationConfiguration.java @@ -0,0 +1,97 @@ +/* + * Copyright 2017 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 java.util.Map; +import java.util.Optional; +import java.util.Properties; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.data.gemfire.config.annotation.support.AutoConfiguredAuthenticationInitializer; +import org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport; +import org.springframework.data.gemfire.util.PropertiesBuilder; +import org.springframework.util.StringUtils; + +/** + * The {@link AutoConfiguredAuthenticationConfiguration} class is a Spring {@link Configuration @Configuration} class + * that auto-configures Pivotal GemFire / Apache Geode Authentication by providing a implementation + * of the {@link org.apache.geode.security.AuthInitialize} interface along with setting the necessary GemFire / Geode + * properties. + * + * @author John Blum + * @see java.util.Properties + * @see org.apache.geode.security.AuthInitialize + * @see org.springframework.context.annotation.Condition + * @see org.springframework.context.annotation.Conditional + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.core.env.Environment + * @see org.springframework.data.gemfire.config.annotation.EnableBeanFactoryLocator + * @see org.springframework.data.gemfire.config.annotation.support.AutoConfiguredAuthenticationInitializer + * @see org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport + * @see org.springframework.data.gemfire.util.PropertiesBuilder + * @since 2.0.0 + */ +@Configuration +@EnableBeanFactoryLocator +@Conditional(AutoConfiguredAuthenticationConfiguration.AutoConfiguredAuthenticationCondition.class) +public class AutoConfiguredAuthenticationConfiguration extends EmbeddedServiceConfigurationSupport { + + protected static final String AUTO_CONFIGURED_AUTH_INIT_STATIC_FACTORY_METHOD = + AutoConfiguredAuthenticationInitializer.class.getName().concat(".newAuthenticationInitializer"); + + protected static final String SECURITY_CLIENT_AUTH_INIT = "security-client-auth-init"; + protected static final String SECURITY_PEER_AUTH_INIT = "security-peer-auth-init"; + + /* (non-Javadoc) */ + @Override + protected Class getAnnotationType() { + return EnableSecurity.class; + } + + /* (non-Javadoc) */ + @Override + protected Properties toGemFireProperties(Map annotationAttributes) { + + return Optional.of(PropertiesBuilder.create()) + .filter(builder -> isMatch(environment())) + .map(builder -> builder + .setProperty(SECURITY_CLIENT_AUTH_INIT, AUTO_CONFIGURED_AUTH_INIT_STATIC_FACTORY_METHOD) + .setProperty(SECURITY_PEER_AUTH_INIT, AUTO_CONFIGURED_AUTH_INIT_STATIC_FACTORY_METHOD)) + .map(PropertiesBuilder::build) + .orElse(null); + } + + /* (non-Javadoc) */ + private static boolean isMatch(Environment environment) { + return StringUtils.hasText(environment.getProperty( + AutoConfiguredAuthenticationInitializer.SDG_SECURITY_USERNAME_PROPERTY)); + } + + public static class AutoConfiguredAuthenticationCondition implements Condition { + + @Override + @SuppressWarnings("all") + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + return isMatch(conditionContext.getEnvironment()); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java index c9b54ac5..7cefeb20 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java @@ -47,7 +47,8 @@ import org.springframework.context.annotation.Import; @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented -@Import({ ApacheShiroSecurityConfiguration.class, GeodeIntegratedSecurityConfiguration.class }) +@Import({ ApacheShiroSecurityConfiguration.class, AutoConfiguredAuthenticationConfiguration.class, + GeodeIntegratedSecurityConfiguration.class }) @UsesGemFireProperties @SuppressWarnings({ "unused" }) public @interface EnableSecurity { @@ -117,6 +118,24 @@ public @interface EnableSecurity { */ String securityPostProcessorClassName() default ""; + /** + * The {@literal security-username} used by a GemFire cache client application required to authenticate. + * + * Defaults to unset. + * + * Use the {@literal spring.data.gemfire.security.username} in {@literal application.properties}. + */ + String securityUsername() default ""; + + /** + * The {@literal security-password} used by a GemFire cache client application required to authenticate. + * + * Defaults to unset. + * + * Use the {@literal spring.data.gemfire.security.password} in {@literal application.properties}. + */ + String securityPassword() default ""; + /** * Sets the Geode System Property referring to the location of an Apache Shiro INI file used to configure * the Apache Shiro Security Framework to secure Apache Geode. diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java index 1ed934b4..3968f1ee 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java @@ -61,7 +61,6 @@ public class GeodeIntegratedSecurityConfiguration extends EmbeddedServiceConfigu */ protected boolean isShiroSecurityConfigured() { try { - // NOTE experimental... //return resolveBean(ApacheShiroSecurityConfiguration.class).isRealmsPresent(); return false; } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAuthInitialize.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAuthInitialize.java new file mode 100644 index 00000000..a15385e3 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAuthInitialize.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 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.support; + +import java.util.Optional; +import java.util.Properties; + +import org.apache.geode.LogWriter; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.security.AuthInitialize; +import org.apache.geode.security.AuthenticationFailedException; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.data.gemfire.support.WiringDeclarableSupport; + +/** + * The {@link AbstractAuthInitialize} class is an abstract support class and basic implementation + * of the {@link AuthInitialize} interface used in the authentication of a client or peer + * with a secure GemFire/Geode cluster. + * + * @author John Blum + * @see java.util.Properties + * @see org.apache.geode.security.AuthInitialize + * @see org.springframework.context.EnvironmentAware + * @see org.springframework.core.env.Environment + * @see org.springframework.data.gemfire.support.WiringDeclarableSupport + * @since 2.0.0 + */ +public abstract class AbstractAuthInitialize extends WiringDeclarableSupport + implements AuthInitialize, EnvironmentAware { + + private Environment environment; + + /** + * Sets a reference to the configured Spring {@link Environment}. + * + * @param environment reference to the configured Spring {@link Environment}. + * @see org.springframework.core.env.Environment + */ + @Override + @SuppressWarnings("all") + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + /** + * Returns a reference to the configured Spring {@link Environment}. + * + * @return a reference to the configured Spring {@link Environment}. + * @see org.springframework.core.env.Environment + */ + protected Optional getEnvironment() { + return Optional.ofNullable(this.environment); + } + + /* (non-Javadoc */ + @Override + @SuppressWarnings("deprecation") + public final void init(LogWriter logWriter, LogWriter logWriter1) throws AuthenticationFailedException { + doInit(); + } + + protected void doInit() { + } + + /* (non-Javadoc */ + @Override + public final Properties getCredentials(Properties properties, DistributedMember distributedMember, boolean isPeer) + throws AuthenticationFailedException { + + return doGetCredentials(properties); + } + + protected abstract Properties doGetCredentials(Properties properties); + + /* (non-Javadoc */ + @Override + public void close() { + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/AutoConfiguredAuthenticationInitializer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AutoConfiguredAuthenticationInitializer.java new file mode 100644 index 00000000..0dd0472a --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AutoConfiguredAuthenticationInitializer.java @@ -0,0 +1,66 @@ +/* + * Copyright 2017 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.support; + +import java.util.Properties; + +import org.apache.shiro.util.StringUtils; + +/** + * The AutoConfiguredAuthenticationInitializer class... + * + * @author John Blum + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class AutoConfiguredAuthenticationInitializer extends AbstractAuthInitialize { + + public static final String SDG_SECURITY_USERNAME_PROPERTY = "spring.data.gemfire.security.username"; + public static final String SDG_SECURITY_PASSWORD_PROPERTY = "spring.data.gemfire.security.password"; + + public static final String SECURITY_USERNAME_PROPERTY = "security-username"; + public static final String SECURITY_PASSWORD_PROPERTY = "security-password"; + + /** + * Factory method used to construct a new instance of {@link AutoConfiguredAuthenticationInitializer}. + * + * @return a new instance of {@link AutoConfiguredAuthenticationInitializer}. + * @see org.springframework.data.gemfire.config.annotation.support.AutoConfiguredAuthenticationInitializer + */ + public static AutoConfiguredAuthenticationInitializer newAuthenticationInitializer() { + return new AutoConfiguredAuthenticationInitializer(); + } + + /* (non-Javadoc) */ + @Override + protected Properties doGetCredentials(Properties properties) { + + getEnvironment() + .filter(environment -> StringUtils.hasText(environment.getProperty(SDG_SECURITY_USERNAME_PROPERTY))) + .ifPresent(environment -> { + + String securityUsername = environment.getProperty(SDG_SECURITY_USERNAME_PROPERTY); + String securityPassword = environment.getProperty(SDG_SECURITY_PASSWORD_PROPERTY); + + properties.setProperty(SECURITY_USERNAME_PROPERTY, securityUsername); + properties.setProperty(SECURITY_PASSWORD_PROPERTY, securityPassword); + + }); + + return properties; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/support/GemfireBeanFactoryLocator.java b/src/main/java/org/springframework/data/gemfire/support/GemfireBeanFactoryLocator.java index 09563d1f..4836041b 100644 --- a/src/main/java/org/springframework/data/gemfire/support/GemfireBeanFactoryLocator.java +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireBeanFactoryLocator.java @@ -77,8 +77,11 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see #afterPropertiesSet() */ public static GemfireBeanFactoryLocator newBeanFactoryLocator() { + GemfireBeanFactoryLocator beanFactoryLocator = new GemfireBeanFactoryLocator(); + beanFactoryLocator.afterPropertiesSet(); + return beanFactoryLocator; } @@ -99,6 +102,7 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see #afterPropertiesSet() */ public static GemfireBeanFactoryLocator newBeanFactoryLocator(BeanFactory beanFactory, String associatedBeanName) { + Assert.isTrue(beanFactory == null || StringUtils.hasText(associatedBeanName), "associatedBeanName must be specified when BeanFactory is not null"); @@ -121,6 +125,7 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see org.springframework.beans.factory.BeanFactory */ protected static BeanFactory resolveBeanFactory(String beanFactoryKey) { + BeanFactory beanFactory = BEAN_FACTORIES.get(beanFactoryKey); Assert.isTrue(BEAN_FACTORIES.isEmpty() || beanFactory != null, @@ -142,12 +147,15 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see org.springframework.beans.factory.BeanFactory */ protected static synchronized BeanFactory resolveSingleBeanFactory() { + if (!BEAN_FACTORIES.isEmpty()) { + boolean allTheSameBeanFactory = true; BeanFactory currentBeanFactory = null; for (BeanFactory beanFactory : BEAN_FACTORIES.values()) { + allTheSameBeanFactory &= nullOrEquals(currentBeanFactory, beanFactory); currentBeanFactory = beanFactory; @@ -178,10 +186,12 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see org.springframework.beans.factory.BeanFactory */ protected static synchronized void registerAliases(Set names, BeanFactory beanFactory) { + Assert.isTrue(nullSafeSet(names).isEmpty() || beanFactory != null, "BeanFactory must not be null when aliases are specified"); for (String name : nullSafeSet(names)) { + BeanFactory existingBeanFactory = BEAN_FACTORIES.putIfAbsent(name, beanFactory); Assert.isTrue(nullOrEquals(existingBeanFactory, beanFactory), @@ -204,6 +214,7 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar */ @Override public void afterPropertiesSet() { + BeanFactory beanFactory = getBeanFactory(); registerAliases(resolveAndInitializeBeanNamesWithAliases(beanFactory), beanFactory); @@ -221,9 +232,11 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see #getAssociatedBeanName() */ Set resolveAndInitializeBeanNamesWithAliases(BeanFactory beanFactory) { + String associatedBeanName = getAssociatedBeanName(); if (beanFactory != null && StringUtils.hasText(associatedBeanName)) { + String[] beanAliases = beanFactory.getAliases(associatedBeanName); this.associatedBeanNameWithAliases = new TreeSet<>(); @@ -272,8 +285,8 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see #resolveSingleBeanFactory() */ public BeanFactory useBeanFactory(String beanFactoryKey) { - return newBeanFactoryReference(StringUtils.hasText(beanFactoryKey) ? resolveBeanFactory(beanFactoryKey) - : resolveSingleBeanFactory()).get(); + return newBeanFactoryReference(StringUtils.hasText(beanFactoryKey) + ? resolveBeanFactory(beanFactoryKey) : resolveSingleBeanFactory()).get(); } /** @@ -380,6 +393,7 @@ public class GemfireBeanFactoryLocator implements BeanFactoryAware, BeanNameAwar * @see org.springframework.beans.factory.BeanFactory */ public BeanFactory get() { + BeanFactory beanFactory = this.beanFactory.get(); Assert.state(beanFactory != null, UNINITIALIZED_BEAN_FACTORY_REFERENCE_MESSAGE); diff --git a/src/main/java/org/springframework/data/gemfire/support/WiringDeclarableSupport.java b/src/main/java/org/springframework/data/gemfire/support/WiringDeclarableSupport.java index 831d21d2..76e96543 100644 --- a/src/main/java/org/springframework/data/gemfire/support/WiringDeclarableSupport.java +++ b/src/main/java/org/springframework/data/gemfire/support/WiringDeclarableSupport.java @@ -85,6 +85,7 @@ public abstract class WiringDeclarableSupport extends DeclarableSupport { * @see #newBeanConfigurer(BeanFactory, String) */ protected boolean configureThis(BeanFactory beanFactory, String templateBeanName) { + BeanConfigurerSupport beanConfigurer = newBeanConfigurer(beanFactory, templateBeanName); beanConfigurer.configureBean(this); @@ -122,11 +123,13 @@ public abstract class WiringDeclarableSupport extends DeclarableSupport { * @see org.springframework.beans.factory.BeanFactory */ protected BeanConfigurerSupport newBeanConfigurer(BeanFactory beanFactory, String templateBeanName) { + BeanConfigurerSupport beanConfigurer = new BeanConfigurerSupport(); beanConfigurer.setBeanFactory(beanFactory); if (StringUtils.hasText(templateBeanName)) { + Assert.isTrue(beanFactory.containsBean(templateBeanName), String.format("Cannot find bean with name [%s]", templateBeanName)); diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/AutoConfiguredAuthenticationConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/AutoConfiguredAuthenticationConfigurationIntegrationTests.java new file mode 100644 index 00000000..9c94d8d2 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/AutoConfiguredAuthenticationConfigurationIntegrationTests.java @@ -0,0 +1,172 @@ +/* + * Copyright 2017 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.springframework.data.gemfire.config.annotation.TestSecurityManager.SECURITY_PASSWORD; +import static org.springframework.data.gemfire.config.annotation.TestSecurityManager.SECURITY_USERNAME; + +import java.util.Optional; + +import org.apache.geode.cache.CacheLoader; +import org.apache.geode.cache.CacheLoaderException; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.LoaderHelper; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.process.ProcessWrapper; +import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport; +import org.springframework.mock.env.MockPropertySource; + +/** + * The AutoConfiguredAuthenticationConfigurationIntegrationTests class... + * + * @author John Blum + * @since 1.0.0 + */ +public class AutoConfiguredAuthenticationConfigurationIntegrationTests extends ClientServerIntegrationTestsSupport { + + private static final int PORT = 42124; + + private static ProcessWrapper gemfireServerProcess; + + private ConfigurableApplicationContext applicationContext; + + @BeforeClass + public static void setupGemFireServer() throws Exception { + + gemfireServerProcess = run(TestGemFireServerConfiguration.class, String.format("-Dgemfire.name=%1$s", + asApplicationName(AutoConfiguredAuthenticationConfigurationIntegrationTests.class))); + + waitForServerToStart("localhost", PORT); + } + + @AfterClass + public static void tearDownGemFireServer() { + stop(gemfireServerProcess); + } + + @After + public void tearDown() { + Optional.ofNullable(this.applicationContext).ifPresent(ConfigurableApplicationContext::close); + } + + private ConfigurableApplicationContext newApplicationContext(PropertySource testPropertySource, + Class... annotatedClasses) { + + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + + MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources(); + + propertySources.addFirst(testPropertySource); + + applicationContext.registerShutdownHook(); + applicationContext.register(annotatedClasses); + applicationContext.refresh(); + + return applicationContext; + } + + @Test + @Ignore + @SuppressWarnings("unchecked") + public void clientAuthenticatedWithServer() { + + MockPropertySource testPropertySource = new MockPropertySource() + .withProperty("spring.data.gemfire.security.username", SECURITY_USERNAME) + .withProperty("spring.data.gemfire.security.password", SECURITY_PASSWORD); + + this.applicationContext = newApplicationContext(testPropertySource, TestGemFireClientConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + assertThat(this.applicationContext.containsBean("Echo")).isTrue(); + + Region echo = this.applicationContext.getBean("Echo", Region.class); + + assertThat(echo.get("TEST")).isEqualTo("TEST"); + } + + @EnableSecurity + @ClientCacheApplication(servers = @ClientCacheApplication.Server(port = PORT)) + static class TestGemFireClientConfiguration { + + @Bean("Echo") + ClientRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + + ClientRegionFactoryBean echoRegion = new ClientRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setClose(false); + echoRegion.setShortcut(ClientRegionShortcut.PROXY); + + return echoRegion; + } + } + + @CacheServerApplication(port = PORT) + @EnableSecurity(securityManagerClassName = "org.springframework.data.gemfire.config.annotation.TestSecurityManager") + static class TestGemFireServerConfiguration { + + public static void main(String[] args) { + + ConfigurableApplicationContext applicationContext = + new AnnotationConfigApplicationContext(TestGemFireServerConfiguration.class); + + applicationContext.registerShutdownHook(); + } + + @Bean("Echo") + LocalRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + + LocalRegionFactoryBean echoRegion = new LocalRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setCacheLoader(newEchoCacheLoader()); + echoRegion.setClose(false); + echoRegion.setPersistent(false); + + return echoRegion; + } + + private CacheLoader newEchoCacheLoader() { + + return new CacheLoader() { + + @Override + public Object load(LoaderHelper loaderHelper) throws CacheLoaderException { + return loaderHelper.getKey(); + } + + @Override + public void close() { + } + }; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/TestSecurityManager.java b/src/test/java/org/springframework/data/gemfire/config/annotation/TestSecurityManager.java new file mode 100644 index 00000000..0f8ae926 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/TestSecurityManager.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 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.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.security.Principal; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.geode.security.AuthenticationFailedException; +import org.springframework.util.ObjectUtils; + +/** + * The {@link TestSecurityManager} class is an Apache Geode / Pivotal GemFire + * {@link org.apache.geode.security.SecurityManager} implementation used for testing purposes. + * + * @author John Blum + * @see java.security.Principal + * @see java.util.Properties + * @see org.apache.geode.security.SecurityManager + * @since 2.0.0 + */ +public final class TestSecurityManager implements org.apache.geode.security.SecurityManager { + + public static final String SECURITY_USERNAME_PROPERTY = "security-username"; + public static final String SECURITY_PASSWORD_PROPERTY = "security-password"; + + public static final String SECURITY_USERNAME = "testUser"; + public static final String SECURITY_PASSWORD = "testP@55w0rd"; + + private final ConcurrentMap authorizedUsers; + + public TestSecurityManager() { + this.authorizedUsers = new ConcurrentHashMap<>(); + this.authorizedUsers.putIfAbsent(SECURITY_USERNAME, SECURITY_PASSWORD); + } + + @Override + public Object authenticate(Properties properties) throws AuthenticationFailedException { + + String username = properties.getProperty(SECURITY_USERNAME_PROPERTY); + String password = properties.getProperty(SECURITY_PASSWORD_PROPERTY); + + return validateIdentified(isIdentified(username, password) ? newPrincipal(username) : null, username); + } + + private boolean isIdentified(String username, String password) { + return ObjectUtils.nullSafeEquals(this.authorizedUsers.get(String.valueOf(username)), password); + } + + private Principal newPrincipal(String username) { + + Principal mockPrincipal = mock(Principal.class, username); + + when(mockPrincipal.getName()).thenReturn(username); + + return mockPrincipal; + } + + private Principal validateIdentified(Principal principal, String username) { + + if (principal == null) { + throw new AuthenticationFailedException(String.format("User [%s] is not valid", username)); + } + + return principal; + } +}