From 0dffb938fc91f71b363979ac6564185d2803c8ad Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 2 Nov 2016 01:09:11 -0700 Subject: [PATCH] SGF-559 - Add integration tests convering Geode Integrated Security framework configuration using Geode and Apache Shiro methods with SDG's Annotation configuration support. --- build.gradle | 8 + .../data/gemfire/util/CollectionUtils.java | 21 + ...ledClientCacheRegionsIntegrationTests.java | 4 +- ...AbstractGeodeSecurityIntegrationTests.java | 404 ++++++++++++++++++ ...yManagerGeodeSecurityIntegrationTests.java | 144 +++++++ ...ShiroIniGeodeSecurityIntegrationTests.java | 54 +++ ...iroRealmGeodeSecurityIntegrationTests.java | 67 +++ ...erverCacheApplicationIntegrationTests.java | 93 +--- .../PeerCacheApplicationIntegrationTests.java | 19 +- .../AsyncEventQueueNamespaceTest.java | 2 +- .../data/gemfire/process/ProcessExecutor.java | 25 +- .../data/gemfire/process/ProcessWrapper.java | 127 +++--- .../gemfire/process/support/ProcessUtils.java | 30 +- .../ClientServerIntegrationTestsSupport.java | 154 ++++++- .../gemfire/test/support/FileSystemUtils.java | 7 +- src/test/resources/logback.xml | 6 +- .../AsyncEventQueueNamespaceTest-context.xml | 2 +- src/test/resources/shiro.ini | 42 ++ src/test/resources/shiro.properties | 29 ++ 19 files changed, 1062 insertions(+), 176 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java rename src/test/java/org/springframework/data/gemfire/config/{ => xml}/AsyncEventQueueNamespaceTest.java (98%) rename src/test/resources/org/springframework/data/gemfire/config/{ => xml}/AsyncEventQueueNamespaceTest-context.xml (94%) create mode 100644 src/test/resources/shiro.ini create mode 100644 src/test/resources/shiro.properties diff --git a/build.gradle b/build.gradle index 29d044b9..e54095e5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import java.util.stream.Collectors + buildscript { repositories { maven { url 'https://repo.spring.io/plugins-release' } @@ -79,6 +81,12 @@ tasks.withType(Test).all { systemProperties['org.springframework.data.gemfire.test.GemfireTestRunner.nomock'] = System.getProperty('org.springframework.data.gemfire.test.GemfireTestRunner.nomock') systemProperties['product.name'] = 'Apache Geode' systemProperties['spring.profiles.active'] = 'apache-geode' + System.getProperties().findAll({ entry -> + entry.key.startsWith("gemfire") || entry.key.startsWith("geode") || entry.key.startsWith("sdg") + }).each { entry -> + systemProperties["$entry.key"] = entry.value + } + //systemProperties.each { entry -> System.out.println(entry.key + '=' + entry.value)} } repositories { diff --git a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java index 50b505c6..8bb552c9 100644 --- a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import java.util.TreeMap; import org.springframework.util.Assert; @@ -53,6 +54,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @param elements array of objects to add to the {@link Set}. * @return an unmodifiable {@link Set} containing the elements from the given object array. */ + @SafeVarargs public static Set asSet(T... elements) { Set set = new HashSet(elements.length); Collections.addAll(set, elements); @@ -219,4 +221,23 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio return result; } + + /* (non-Javadoc) */ + public static String toString(Map map) { + StringBuilder builder = new StringBuilder("{\n"); + + int count = 0; + + for (Map.Entry entry : new TreeMap<>(map).entrySet()) { + builder.append(++count > 1 ? ",\n" : ""); + builder.append("\t"); + builder.append(entry.getKey()); + builder.append(" = "); + builder.append(entry.getValue()); + } + + builder.append("\n}"); + + return builder.toString(); + } } diff --git a/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java index 57143d5b..fbc71c27 100644 --- a/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java @@ -102,9 +102,9 @@ public class GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationT @BeforeClass public static void setupGemFireCluster() throws Exception { serverOne = ProcessExecutor.launch(createDirectory("serverOne"), GemFireCacheServerOneConfiguration.class); - waitForCacheServerToStart("localhost", 41414); + waitForServerToStart("localhost", 41414); serverTwo = ProcessExecutor.launch(createDirectory("serverTwo"), GemFireCacheServerTwoConfiguration.class); - waitForCacheServerToStart("localhost", 42424); + waitForServerToStart("localhost", 42424); } @AfterClass diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..c8b43730 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java @@ -0,0 +1,404 @@ +/* + * Copyright 2016 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.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.io.Serializable; +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +import org.apache.geode.LogWriter; +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.apache.geode.cache.client.ServerOperationException; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.security.AuthInitialize; +import org.apache.geode.security.AuthenticationFailedException; +import org.apache.geode.security.NotAuthorizedException; +import org.apache.geode.security.ResourcePermission; +import org.apache.shiro.util.Assert; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runners.MethodSorters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +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.data.gemfire.util.CollectionUtils; +import org.springframework.data.gemfire.util.PropertiesBuilder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.util.StringUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Abstract base test class for implementing Apache Geode Integrated Security Integration Tests. + * + * @author John Blum + * @see org.junit.FixMethodOrder + * @see org.junit.Test + * @see lombok + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport + * @since 1.0.0 + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@SuppressWarnings("unused") +public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServerIntegrationTestsSupport { + + protected static final int CACHE_SERVER_PORT = 13531; + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractGeodeSecurityIntegrationTests.class); + + protected static final String CACHE_SERVER_HOST = "localhost"; + protected static final String GEODE_SECURITY_PROFILE_PROPERTY = "geode.security.profile"; + protected static final String SECURITY_PASSWORD_PROPERTY = "security-password"; + protected static final String SECURITY_USERNAME_PROPERTY = "security-username"; + + private static final AtomicInteger RUN_COUNT = new AtomicInteger(0); + + private static ProcessWrapper geodeServerProcess; + + @BeforeClass + public static void runGeodeServer() throws IOException { + String geodeSecurityProfile = System.getProperty(GEODE_SECURITY_PROFILE_PROPERTY); + + if (StringUtils.hasText(geodeSecurityProfile)) { + runGeodeServer(geodeSecurityProfile); + } + } + + protected static ProcessWrapper runGeodeServer(String geodeSecurityProfile) throws IOException { + Assert.hasText(geodeSecurityProfile, String.format("The '%s' System property must be set", + GEODE_SECURITY_PROFILE_PROPERTY)); + + geodeServerProcess = run(GeodeServerConfiguration.class, + String.format("-Dgemfire.log-file=%s", logFile()), + String.format("-Dgemfire.log-level=%s", logLevel(TEST_GEMFIRE_LOG_LEVEL)), + String.format("-Dspring.profiles.active=apache-geode-server,%s", geodeSecurityProfile)); + + waitForServerToStart(CACHE_SERVER_HOST, CACHE_SERVER_PORT); + + return geodeServerProcess; + } + + @AfterClass + public static void stopGeodeServer() { + System.clearProperty(GEODE_SECURITY_PROFILE_PROPERTY); + stop(geodeServerProcess); + } + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Resource(name = "Echo") + private Region echo; + + @Test + @DirtiesContext + public void authorizedUser() { + assertThat(echo.get("one")).isEqualTo("one"); + assertThat(echo.put("two", "four")).isNull(); + assertThat(echo.get("two")).isEqualTo("four"); + } + + @Test + public void unauthorizedUser() { + assertThat(echo.get("one")).isEqualTo("one"); + + exception.expect(ServerOperationException.class); + exception.expectCause(is(instanceOf(NotAuthorizedException.class))); + exception.expectMessage(containsString("analyst not authorized for DATA:WRITE:Echo:two")); + + echo.put("two", "four"); + } + + protected static abstract class AuthInitializeSupport implements AuthInitialize { + + /** + * @inheritDoc + */ + @Override + public void init(LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException { + } + + /** + * @inheritDoc + */ + @Override + public Properties getCredentials(Properties securityProperties, DistributedMember server, boolean isPeer) + throws AuthenticationFailedException { + + return getCredentials(securityProperties); + } + + /** + * @inheritDoc + */ + @Override + public void close() { + } + } + + public static class GeodeClientAuthInitialize extends AuthInitializeSupport { + + protected static final User ANALYST = User.newUser("analyst").with("p@55w0rd"); + protected static final User SCIENTIST = User.newUser("scientist").with("w0rk!ng4u"); + + private final User user; + + /* (non-Javadoc) */ + public static GeodeClientAuthInitialize create() { + return new GeodeClientAuthInitialize(RUN_COUNT.incrementAndGet() < 2 ? SCIENTIST : ANALYST); + } + + /* (non-Javadoc) */ + public GeodeClientAuthInitialize(User user) { + Assert.notNull(user, "User cannot be null"); + this.user = user; + } + + /** + * @inheritDoc + */ + @Override + public Properties getCredentials(Properties securityProperties) { + User user = getUser(); + + return new PropertiesBuilder() + .setProperty(SECURITY_USERNAME_PROPERTY, user.getName()) + .setProperty(SECURITY_PASSWORD_PROPERTY, user.getCredentials()) + .build(); + } + + /* (non-Javadoc) */ + protected User getUser() { + return this.user; + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + User user = getUser(); + return String.format("%1$s:%2$s", user.getName(), user.getCredentials()); + } + } + + @ClientCacheApplication(name = "GeodeSecurityIntegrationTestsClient", logLevel = TEST_GEMFIRE_LOG_LEVEL, + servers = { @ClientCacheApplication.Server(port = CACHE_SERVER_PORT) }) + @EnableAuth(clientAuthenticationInitializer = "org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests$GeodeClientAuthInitialize.create") + @Profile("apache-geode-client") + static class GeodeClientConfiguration { + + @Bean("Echo") + ClientRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + ClientRegionFactoryBean echoRegion = new ClientRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setClose(false); + echoRegion.setShortcut(ClientRegionShortcut.PROXY); + + return echoRegion; + } + } + + @CacheServerApplication(name = "GeodeSecurityIntegrationTestsServer", logLevel = TEST_GEMFIRE_LOG_LEVEL, + port = CACHE_SERVER_PORT) + @Import({ + ApacheShiroIniGeodeSecurityIntegrationTests.ApacheShiroIniConfiguration.class, + ApacheShiroRealmGeodeSecurityIntegrationTests.ApacheShiroRealmConfiguration.class, + ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.ApacheGeodeSecurityManagerConfiguration.class + }) + @Profile("apache-geode-server") + public static class GeodeServerConfiguration { + + public static void main(String[] args) { + runSpringApplication(GeodeServerConfiguration.class, args); + } + + @Autowired + private GemFireCache gemfireCache; + + @Bean("Echo") + LocalRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + LocalRegionFactoryBean echoRegion = new LocalRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setCacheLoader(echoCacheLoader()); + echoRegion.setClose(false); + echoRegion.setPersistent(false); + + return echoRegion; + } + + CacheLoader echoCacheLoader() { + return new CacheLoader() { + @Override + public String load(LoaderHelper helper) throws CacheLoaderException { + return helper.getKey(); + } + + @Override + public void close() { + } + }; + } + + @PostConstruct + public void postProcess() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Geode Distributed System Properties [{}]", + CollectionUtils.toString(gemfireCache.getDistributedSystem().getProperties())); + } + } + } + + @Getter + @EqualsAndHashCode(of = { "name", "credentials" }) + @RequiredArgsConstructor(staticName = "newUser") + @SuppressWarnings("all") + public static class User implements Iterable, Principal, Serializable { + + private Set roles = new HashSet<>(); + + @NonNull + private String name; + private String credentials; + + /* (non-Javadoc) */ + public boolean hasPermission(ResourcePermission permission) { + for (Role role : this) { + if (role.hasPermission(permission)) { + return true; + } + } + + return false; + } + + /* (non-Javadoc) */ + public boolean hasRole(Role role) { + return this.roles.contains(role); + } + + /** + * @inheritDoc + */ + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.roles).iterator(); + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return getName(); + } + + /* (non-Javadoc) */ + public User with(String credentials) { + this.credentials = credentials; + return this; + } + + /* (non-Javadoc) */ + public User with(Role... roles) { + Collections.addAll(this.roles, roles); + return this; + } + } + + @Getter + @EqualsAndHashCode(of = "name") + @RequiredArgsConstructor(staticName = "newRole") + @SuppressWarnings("unsed") + public static class Role implements Iterable, Serializable { + + @NonNull + private String name; + + private Set permissions = new HashSet<>(); + + /* (non-Javadoc) */ + public boolean hasPermission(ResourcePermission permission) { + for (ResourcePermission thisPermission : this) { + if (thisPermission.implies(permission)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.permissions).iterator(); + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return getName(); + } + + /* (non-Javadoc) */ + public Role with(ResourcePermission... permissions) { + Collections.addAll(this.permissions, permissions); + return this; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..29894639 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2016 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.apache.geode.security.ResourcePermission.Operation; +import static org.apache.geode.security.ResourcePermission.Resource; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import org.apache.geode.security.AuthenticationFailedException; +import org.apache.geode.security.ResourcePermission; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for Apache Geode Integrated Security using an application-specific, Apache Geode + * {@link org.apache.geode.security.SecurityManager}. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = AbstractGeodeSecurityIntegrationTests.GeodeClientConfiguration.class) +@ActiveProfiles("apache-geode-client") +public class ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests extends AbstractGeodeSecurityIntegrationTests { + + protected static final String GEODE_SECURITY_MANAGER_PROPERTY_CONFIGURATION_PROFILE = + "geode-security-manager-property-configuration"; + + @BeforeClass + public static void setup() throws IOException { + runGeodeServer(GEODE_SECURITY_MANAGER_PROPERTY_CONFIGURATION_PROFILE); + } + + @Configuration + @EnableSecurity(securityManagerClassName = "org.springframework.data.gemfire.config.annotation.ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests$TestGeodeSecurityManager") + @Profile(GEODE_SECURITY_MANAGER_PROPERTY_CONFIGURATION_PROFILE) + public static class ApacheGeodeSecurityManagerConfiguration { + } + + protected interface GeodeSecurityRepository { + + ResourcePermission CLUSTER_MANAGE = new ResourcePermission(Resource.CLUSTER, Operation.MANAGE); + ResourcePermission CLUSTER_READ = new ResourcePermission(Resource.CLUSTER, Operation.READ); + ResourcePermission CLUSTER_WRITE = new ResourcePermission(Resource.CLUSTER, Operation.WRITE); + + ResourcePermission DATA_MANAGE = new ResourcePermission(Resource.DATA, Operation.MANAGE); + ResourcePermission DATA_READ = new ResourcePermission(Resource.DATA, Operation.READ); + ResourcePermission DATA_WRITE = new ResourcePermission(Resource.DATA, Operation.WRITE); + + Role ADMIN = Role.newRole("ADMIN").with(CLUSTER_MANAGE, CLUSTER_READ, CLUSTER_WRITE); + Role DBA = Role.newRole("DBA").with(DATA_MANAGE, DATA_READ, DATA_WRITE); + Role DATA_SCIENTIST = Role.newRole("DATA_SCIENTIST").with(DATA_READ, DATA_WRITE); + Role DATA_ANALYST = Role.newRole("DATA_ANALYST").with(DATA_READ); + Role GUEST = Role.newRole("GUEST"); + + User root = User.newUser("root").with("s3cr3t!").with(ADMIN, DBA); + User scientist = User.newUser("scientist").with("w0rk!ng4u").with(DATA_SCIENTIST); + User analyst = User.newUser("analyst").with("p@55w0rd").with(DATA_ANALYST); + User guest = User.newUser("guest").with("guest").with(GUEST); + + Set users = new HashSet<>(Arrays.asList(root, scientist, analyst, guest)); + + default User findBy(String username) { + return users.stream().filter((user) -> user.getName().equals(username)).findFirst().get(); + } + } + + public static abstract class GeodeSecurityManagerSupport implements org.apache.geode.security.SecurityManager { + + /** + * @inheritDoc + */ + @Override + public void init(Properties securityProps) { + } + + /** + * @inheritDoc + */ + @Override + public void close() { + } + } + + public static class TestGeodeSecurityManager extends GeodeSecurityManagerSupport { + + private final GeodeSecurityRepository securityRepository; + + public TestGeodeSecurityManager() { + this.securityRepository = new GeodeSecurityRepository() { }; + } + + /** + * @inheritDoc + */ + @Override + public Object authenticate(Properties credentials) throws AuthenticationFailedException { + String username = credentials.getProperty(SECURITY_USERNAME_PROPERTY); + String password = credentials.getProperty(SECURITY_PASSWORD_PROPERTY); + + User user = securityRepository.findBy(username); + + if (user == null || !user.getCredentials().equals(password)) { + throw new AuthenticationFailedException(String.format("Failed to authenticate user [%s]", username)); + } + + return user; + } + + /** + * @inheritDoc + */ + @Override + public boolean authorize(Object principal, ResourcePermission permission) { + return (principal instanceof User && ((User) principal).hasPermission(permission)); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..e196de15 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 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.io.IOException; + +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for Apache Geode Integrated Security using an Apache Shiro INI security configuration resource. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = AbstractGeodeSecurityIntegrationTests.GeodeClientConfiguration.class) +@ActiveProfiles("apache-geode-client") +public class ApacheShiroIniGeodeSecurityIntegrationTests extends AbstractGeodeSecurityIntegrationTests { + + protected static final String SHIRO_INI_CONFIGURATION_PROFILE = "shiro-ini-configuration"; + + @BeforeClass + public static void setup() throws IOException { + runGeodeServer(SHIRO_INI_CONFIGURATION_PROFILE); + } + + @Configuration + @EnableSecurity(shiroIniResourcePath = "shiro.ini") + @Profile(SHIRO_INI_CONFIGURATION_PROFILE) + public static class ApacheShiroIniConfiguration { + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..bf667395 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016 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.io.IOException; + +import org.apache.geode.internal.security.shiro.GeodePermissionResolver; +import org.apache.shiro.realm.text.PropertiesRealm; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for Apache Geode Integrated Security using an Apache Shiro Realm security configuration + * as a Spring managed bean in a Spring {@link org.springframework.context.ApplicationContext}. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = AbstractGeodeSecurityIntegrationTests.GeodeClientConfiguration.class) +@ActiveProfiles("apache-geode-client") +public class ApacheShiroRealmGeodeSecurityIntegrationTests extends AbstractGeodeSecurityIntegrationTests { + + protected static final String SHIRO_REALM_CONFIGURATION_PROFILE = "shiro-realm-configuration"; + + @BeforeClass + public static void setup() throws IOException { + runGeodeServer(SHIRO_REALM_CONFIGURATION_PROFILE); + } + + @Configuration + @EnableSecurity + @Profile(SHIRO_REALM_CONFIGURATION_PROFILE) + @SuppressWarnings("unused") + public static class ApacheShiroRealmConfiguration { + + @Bean + public PropertiesRealm shiroRealm() { + PropertiesRealm propertiesRealm = new PropertiesRealm(); + propertiesRealm.setResourcePath("classpath:shiro.properties"); + propertiesRealm.setPermissionResolver(new GeodePermissionResolver()); + return propertiesRealm; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java index dcd10d22..70611630 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java @@ -17,17 +17,7 @@ package org.springframework.data.gemfire.config.annotation; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.io.File; -import java.io.IOException; -import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import static org.assertj.core.api.Assertions.assertThat; import javax.annotation.Resource; @@ -43,18 +33,13 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.gemfire.PartitionedRegionFactoryBean; import org.springframework.data.gemfire.client.ClientRegionFactoryBean; -import org.springframework.data.gemfire.process.ProcessExecutor; import org.springframework.data.gemfire.process.ProcessWrapper; -import org.springframework.data.gemfire.test.support.FileSystemUtils; -import org.springframework.data.gemfire.test.support.IOUtils; -import org.springframework.data.gemfire.test.support.ThreadUtils; +import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.util.Assert; +import org.springframework.test.context.junit4.SpringRunner; /** * Test suite of test cases testing the contract and functionality of the {@link CacheServerApplication} @@ -66,15 +51,15 @@ import org.springframework.util.Assert; * @see org.springframework.data.gemfire.config.annotation.CacheServerApplication * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication * @see org.springframework.test.context.ContextConfiguration - * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see org.springframework.test.context.junit4.SpringRunner * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.server.CacheServer * @since 1.9.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = ClientServerCacheApplicationIntegrationTests.ClientCacheApplicationConfiguration.class) @SuppressWarnings("all") -public class ClientServerCacheApplicationIntegrationTests { +public class ClientServerCacheApplicationIntegrationTests extends ClientServerIntegrationTestsSupport { private static final int PORT = 12480; @@ -82,56 +67,15 @@ public class ClientServerCacheApplicationIntegrationTests { @BeforeClass public static void setupGemFireServer() throws Exception { - String serverName = ClientServerCacheApplicationIntegrationTests.class.getSimpleName(); + gemfireServerProcess = run(CacheServerApplicationConfiguration.class, String.format("-Dgemfire.name=%1$s", + asApplicationName(ClientServerCacheApplicationIntegrationTests.class))); - File serverWorkingDirectory = new File(FileSystemUtils.WORKING_DIRECTORY, serverName.toLowerCase()); - - Assert.isTrue(serverWorkingDirectory.isDirectory() || serverWorkingDirectory.mkdirs()); - - List arguments = new ArrayList(); - - arguments.add(String.format("-Dgemfire.name=%1$s", serverName)); - - gemfireServerProcess = ProcessExecutor.launch(serverWorkingDirectory, - CacheServerApplicationConfiguration.class, - arguments.toArray(new String[arguments.size()])); - - waitForServerStart(TimeUnit.SECONDS.toMillis(20)); - - System.out.println("GemFire Cache Server Process for Client/Server cache application should be running..."); - } - - private static void waitForServerStart(final long milliseconds) { - ThreadUtils.timedWait(milliseconds, TimeUnit.SECONDS.toMillis(1), new ThreadUtils.WaitCondition() { - AtomicBoolean connected = new AtomicBoolean(false); - - public boolean waiting() { - Socket socket = null; - - try { - if (!connected.get()) { - socket = new Socket("localhost", PORT); - connected.set(true); - } - } - catch (IOException ignore) { - } - finally { - IOUtils.close(socket); - } - - return !connected.get(); - } - }); + waitForServerToStart("localhost", PORT); } @AfterClass public static void tearDownGemFireServer() { - gemfireServerProcess.shutdown(); - - if (Boolean.valueOf(System.getProperty("spring.data.gemfire.fork.clean", Boolean.TRUE.toString()))) { - org.springframework.util.FileSystemUtils.deleteRecursively(gemfireServerProcess.getWorkingDirectory()); - } + stop(gemfireServerProcess); } @Autowired @@ -141,22 +85,19 @@ public class ClientServerCacheApplicationIntegrationTests { private Region echo; @Test - public void clientEchoProxyRegionEchoesKeysForValues() { - assertThat(echo.get("Hello"), is(equalTo("Hello"))); - assertThat(echo.get("Test"), is(equalTo("Test"))); + public void echoClientProxyRegionEchoesKeysForValues() { + assertThat(echo.get("Hello")).isEqualTo("Hello"); + assertThat(echo.get("Test")).isEqualTo("Test"); } - @CacheServerApplication(name = "ClientServerCacheApplicationIntegrationTests", logLevel = "warn", port = 12480) + @CacheServerApplication(name = "ClientServerCacheApplicationIntegrationTests", logLevel = "warn", port = PORT) public static class CacheServerApplicationConfiguration { public static void main(String[] args) { - AnnotationConfigApplicationContext applicationContext = - new AnnotationConfigApplicationContext(CacheServerApplicationConfiguration.class); - - applicationContext.registerShutdownHook(); + runSpringApplication(CacheServerApplicationConfiguration.class, args); } - @Bean(name = "Echo") + @Bean("Echo") public PartitionedRegionFactoryBean echoRegion(Cache gemfireCache) { PartitionedRegionFactoryBean echoRegion = new PartitionedRegionFactoryBean(); @@ -183,7 +124,7 @@ public class ClientServerCacheApplicationIntegrationTests { } } - @ClientCacheApplication(logLevel = "warn", servers = { @ClientCacheApplication.Server(port = 12480)}) + @ClientCacheApplication(logLevel = "warn", servers = { @ClientCacheApplication.Server(port = PORT)}) static class ClientCacheApplicationConfiguration { @Bean(name = "Echo") diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java index ee8115bd..840de2c1 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java @@ -17,9 +17,7 @@ package org.springframework.data.gemfire.config.annotation; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import javax.annotation.Resource; @@ -33,21 +31,20 @@ import org.junit.runner.RunWith; import org.springframework.context.annotation.Bean; import org.springframework.data.gemfire.PartitionedRegionFactoryBean; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; /** - * Test suite of test case testing the contract and functionality of the {@link PeerCacheApplication} SDG annotation - * for configuring and bootstrapping a GemFire peer cache instance. + * Integration tests for {@link PeerCacheApplication} SDG annotation. * * @author John Blum * @see org.junit.Test * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication * @see org.springframework.test.context.ContextConfiguration - * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see org.springframework.test.context.junit4.SpringRunner * @see org.apache.geode.cache.Cache * @since 1.9.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = PeerCacheApplicationIntegrationTests.PeerCacheApplicationConfiguration.class) @SuppressWarnings("all") public class PeerCacheApplicationIntegrationTests { @@ -57,14 +54,14 @@ public class PeerCacheApplicationIntegrationTests { @Test public void echoPartitionRegionEchoesKeysAsValues() { - assertThat(echo.get("Hello"), is(equalTo("Hello"))); - assertThat(echo.get("Test"), is(equalTo("Test"))); + assertThat(echo.get("Hello")).isEqualTo("Hello"); + assertThat(echo.get("Test")).isEqualTo("Test"); } @PeerCacheApplication(name = "PeerCacheApplicationIntegrationTests", logLevel = "warn") static class PeerCacheApplicationConfiguration { - @Bean(name = "Echo") + @Bean("Echo") PartitionedRegionFactoryBean echoRegion(Cache gemfireCache) { PartitionedRegionFactoryBean echoRegion = new PartitionedRegionFactoryBean(); diff --git a/src/test/java/org/springframework/data/gemfire/config/AsyncEventQueueNamespaceTest.java b/src/test/java/org/springframework/data/gemfire/config/xml/AsyncEventQueueNamespaceTest.java similarity index 98% rename from src/test/java/org/springframework/data/gemfire/config/AsyncEventQueueNamespaceTest.java rename to src/test/java/org/springframework/data/gemfire/config/xml/AsyncEventQueueNamespaceTest.java index 1c1c4f1d..5c4eb8d2 100644 --- a/src/test/java/org/springframework/data/gemfire/config/AsyncEventQueueNamespaceTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/xml/AsyncEventQueueNamespaceTest.java @@ -15,7 +15,7 @@ * */ -package org.springframework.data.gemfire.config; +package org.springframework.data.gemfire.config.xml; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java b/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java index 8869cffb..0bb71420 100644 --- a/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java +++ b/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.springframework.data.gemfire.test.support.FileSystemUtils; import org.springframework.util.Assert; @@ -31,6 +32,7 @@ import org.springframework.util.StringUtils; * The {@link ProcessExecutor} class is a utility class for launching and running Java processes. * * @author John Blum + * @see java.io.File * @see java.lang.Process * @see java.lang.ProcessBuilder * @see java.lang.System @@ -67,11 +69,7 @@ public abstract class ProcessExecutor { ProcessWrapper processWrapper = new ProcessWrapper(process, ProcessConfiguration.create(processBuilder)); - processWrapper.register(new ProcessInputStreamListener() { - @Override public void onInput(final String input) { - System.err.printf("[FORK-OUT] - %s%n", input); - } - }); + processWrapper.register((input) -> System.err.printf("[FORK] - %s%n", input)); return processWrapper; } @@ -79,7 +77,7 @@ public abstract class ProcessExecutor { protected static String[] buildCommand(String classpath, Class type, String... args) { Assert.notNull(type, "The main Java class to launch must not be null"); - List command = new ArrayList(); + List command = new ArrayList<>(); List programArgs = Collections.emptyList(); command.add(JAVA_EXE.getAbsolutePath()); @@ -90,7 +88,7 @@ public abstract class ProcessExecutor { command.addAll(getSpringGemFireSystemProperties()); if (args != null) { - programArgs = new ArrayList(args.length); + programArgs = new ArrayList<>(args.length); for (String arg : args) { if (isJvmOption(arg)) { @@ -109,15 +107,10 @@ public abstract class ProcessExecutor { } protected static Collection getSpringGemFireSystemProperties() { - List springGemfireSystemProperties = new ArrayList(); - - for (String property : System.getProperties().stringPropertyNames()) { - if (property.startsWith(SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX)) { - springGemfireSystemProperties.add(String.format("-D%1$s=%2$s", property, System.getProperty(property))); - } - } - - return springGemfireSystemProperties; + return System.getProperties().stringPropertyNames().stream() + .filter(property -> property.startsWith(SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX)) + .map(property -> String.format("-D%1$s=%2$s", property, System.getProperty(property))) + .collect(Collectors.toList()); } protected static boolean isJvmOption(String option) { diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java b/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java index 522ed325..4b2ceb4e 100644 --- a/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java +++ b/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java @@ -18,14 +18,12 @@ package org.springframework.data.gemfire.process; import java.io.BufferedReader; import java.io.File; -import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -61,18 +59,20 @@ public class ProcessWrapper { protected static final long DEFAULT_WAIT_TIME_MILLISECONDS = TimeUnit.SECONDS.toMillis(15); - private final List listeners = new CopyOnWriteArrayList(); + private final List listeners = new CopyOnWriteArrayList<>(); protected final Logger log = Logger.getLogger(getClass().getName()); private final Process process; private final ProcessConfiguration processConfiguration; + /* (non-Javadoc) */ public ProcessWrapper(Process process, ProcessConfiguration processConfiguration) { Assert.notNull(process, "Process must not be null"); - Assert.notNull(processConfiguration, "The context and configuration meta-data providing details about" - + " the environment in which the process is running and how the process was configured must not be null"); + Assert.notNull(processConfiguration, "The context and configuration meta-data providing details" + + " about the environment in which the process is running and how the process was configured and executed" + + " must not be null"); this.process = process; this.processConfiguration = processConfiguration; @@ -80,39 +80,42 @@ public class ProcessWrapper { init(); } + /* (non-Javadoc) */ private void init() { - newThread("Process OUT Stream Reader", newProcessInputStreamReader(process.getInputStream())).start(); + newThread("Process OUT Stream Reader Thread", + newProcessInputStreamReaderRunnable(process.getInputStream())).start(); if (!isRedirectingErrorStream()) { - newThread("Process ERR Stream Reader", newProcessInputStreamReader(process.getErrorStream())).start(); + newThread("Process ERR Stream Reader Thread", + newProcessInputStreamReaderRunnable(process.getErrorStream())).start(); } } - protected Runnable newProcessInputStreamReader(final InputStream in) { - return new Runnable() { - @Override public void run() { - if (isRunning()) { - BufferedReader inputReader = new BufferedReader(new InputStreamReader(in)); + /* (non-Javadoc) */ + protected Runnable newProcessInputStreamReaderRunnable(InputStream in) { + return () -> { + if (isRunning()) { + BufferedReader inputReader = new BufferedReader(new InputStreamReader(in)); - try { - for (String input = inputReader.readLine(); input != null; input = inputReader.readLine()) { - for (ProcessInputStreamListener listener : listeners) { - listener.onInput(input); - } + try { + for (String input = inputReader.readLine(); input != null; input = inputReader.readLine()) { + for (ProcessInputStreamListener listener : listeners) { + listener.onInput(input); } } - catch (IOException ignore) { - // Ignore IO error and just stop reading from the process input stream - // An IO error occurred most likely because the process was terminated - } - finally { - IOUtils.close(inputReader); - } + } + catch (IOException ignore) { + // Ignore IO error and just stop reading from the process input stream + // An IO error occurred most likely because the process was terminated + } + finally { + IOUtils.close(inputReader); } } }; } + /* (non-Javadoc) */ protected Thread newThread(String name, Runnable task) { Assert.hasText(name, "Thread name must be specified"); Assert.notNull(task, "Thread task must not be null"); @@ -125,22 +128,37 @@ public class ProcessWrapper { return thread; } + /* (non-Javadoc) */ + public boolean isAlive() { + return ProcessUtils.isAlive(process); + } + + /* (non-Javadoc) */ + public boolean isNotAlive() { + return !isAlive(); + } + + /* (non-Javadoc) */ public List getCommand() { return processConfiguration.getCommand(); } + /* (non-Javadoc) */ public String getCommandString() { return processConfiguration.getCommandString(); } + /* (non-Javadoc) */ public Map getEnvironment() { return processConfiguration.getEnvironment(); } + /* (non-Javadoc) */ public int getPid() { return ProcessUtils.findAndReadPid(getWorkingDirectory()); } + /* (non-Javadoc) */ public int safeGetPid() { try { return getPid(); @@ -150,22 +168,32 @@ public class ProcessWrapper { } } + /* (non-Javadoc) */ public boolean isRedirectingErrorStream() { return processConfiguration.isRedirectingErrorStream(); } + /* (non-Javadoc) */ + public boolean isNotRunning() { + return !isRunning(); + } + + /* (non-Javadoc) */ public boolean isRunning() { return ProcessUtils.isRunning(process); } + /* (non-Javadoc) */ public File getWorkingDirectory() { return processConfiguration.getWorkingDirectory(); } + /* (non-Javadoc) */ public int exitValue() { return process.exitValue(); } + /* (non-Javadoc) */ public int safeExitValue() { try { return exitValue(); @@ -175,38 +203,37 @@ public class ProcessWrapper { } } + /* (non-Javadoc) */ public String readLogFile() throws IOException { - File[] logFiles = FileSystemUtils.listFiles(getWorkingDirectory(), new FileFilter() { - @Override public boolean accept(File pathname) { - return (pathname != null && (pathname.isDirectory() || pathname.getAbsolutePath().endsWith(".log"))); - } - }); + File[] logFiles = FileSystemUtils.listFiles(getWorkingDirectory(), + (path) -> (path != null && (path.isDirectory() || path.getAbsolutePath().endsWith(".log")))); if (logFiles.length > 0) { return readLogFile(logFiles[0]); } else { throw new FileNotFoundException(String.format( - "No log files found in process's working directory [%s]", getWorkingDirectory())); + "No log files found in process's [%d] working directory [%s]", + safeGetPid(), getWorkingDirectory())); } } + /* (non-Javadoc) */ public String readLogFile(File log) throws IOException { return FileUtils.read(log); } + /* (non-Javadoc) */ public boolean register(ProcessInputStreamListener listener) { return (listener != null && listeners.add(listener)); } + /* (non-Javadoc) */ public void registerShutdownHook() { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override public void run() { - shutdown(); - } - })); + Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); } + /* (non-Javadoc) */ public void signalStop() { try { ProcessUtils.signalStop(process); @@ -220,28 +247,28 @@ public class ProcessWrapper { } } + /* (non-Javadoc) */ public int stop() { return stop(DEFAULT_WAIT_TIME_MILLISECONDS); } + /* (non-Javadoc) */ public int stop(long milliseconds) { if (isRunning()) { boolean interrupted = false; int exitValue = -1; - final int pid = safeGetPid(); - final long timeout = (System.currentTimeMillis() + milliseconds); - final AtomicBoolean exited = new AtomicBoolean(false); + int pid = safeGetPid(); + long timeout = (System.currentTimeMillis() + milliseconds); + AtomicBoolean exited = new AtomicBoolean(false); ExecutorService executorService = Executors.newSingleThreadExecutor(); try { - Future futureExitValue = executorService.submit(new Callable() { - @Override public Integer call() throws Exception { - process.destroy(); - int exitValue = process.waitFor(); - exited.set(true); - return exitValue; - } + Future futureExitValue = executorService.submit(() -> { + process.destroy(); + int localExitValue = process.waitFor(); + exited.set(true); + return localExitValue; }); while (!exited.get() && System.currentTimeMillis() < timeout) { @@ -277,6 +304,7 @@ public class ProcessWrapper { } } + /* (non-Javadoc) */ public int shutdown() { if (isRunning()) { log.info(String.format("Stopping process [%d]...%n", safeGetPid())); @@ -287,19 +315,18 @@ public class ProcessWrapper { return stop(); } + /* (non-Javadoc) */ public boolean unregister(ProcessInputStreamListener listener) { return listeners.remove(listener); } + /* (non-Javadoc) */ public void waitFor() { waitFor(DEFAULT_WAIT_TIME_MILLISECONDS); } + /* (non-Javadoc) */ public void waitFor(long milliseconds) { - ThreadUtils.timedWait(milliseconds, 500, new ThreadUtils.WaitCondition() { - @Override public boolean waiting() { - return isRunning(); - } - }); + ThreadUtils.timedWait(milliseconds, 500, this::isRunning); } } diff --git a/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java b/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java index 03570895..674f8a91 100644 --- a/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java +++ b/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java @@ -42,9 +42,6 @@ import org.springframework.util.StringUtils; * @author John Blum * @see java.io.File * @see java.lang.Process - * @see java.lang.management.ManagementFactory - * @see java.lang.management.RuntimeMXBean - * see com.sun.tools.attach.VirtualMachine * @since 1.5.0 */ @SuppressWarnings("unused") @@ -54,6 +51,7 @@ public abstract class ProcessUtils { protected static final String TERM_TOKEN = ""; + /* (non-Javadoc) */ public static int currentPid() { RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); String runtimeMXBeanName = runtimeMXBean.getName(); @@ -77,6 +75,12 @@ public abstract class ProcessUtils { cause); } + /* (non-Javadoc) */ + public static boolean isAlive(Process process) { + return (process != null && process.isAlive()); + } + + /* (non-Javadoc) */ public static boolean isRunning(int processId) { /* for (VirtualMachineDescriptor vmDescriptor : VirtualMachine.list()) { @@ -87,9 +91,11 @@ public abstract class ProcessUtils { return false; */ + throw new UnsupportedOperationException("operation not supported"); } + /* (non-Javadoc) */ public static boolean isRunning(Process process) { try { process.exitValue(); @@ -100,6 +106,7 @@ public abstract class ProcessUtils { } } + /* (non-Javadoc) */ public static void signalStop(Process process) throws IOException { if (isRunning(process)) { OutputStream processOutputStream = process.getOutputStream(); @@ -108,12 +115,14 @@ public abstract class ProcessUtils { } } + /* (non-Javadoc) */ @SuppressWarnings("all") public static void waitForStopSignal() { Scanner in = new Scanner(System.in); while (!TERM_TOKEN.equals(in.next())); } + /* (non-Javadoc) */ public static int findAndReadPid(File workingDirectory) { File pidFile = findPidFile(workingDirectory); @@ -126,6 +135,7 @@ public abstract class ProcessUtils { return readPid(pidFile); } + /* (non-Javadoc) */ @SuppressWarnings("all") protected static File findPidFile(File workingDirectory) { Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory), String.format( @@ -144,6 +154,7 @@ public abstract class ProcessUtils { return null; } + /* (non-Javadoc) */ @SuppressWarnings("all") public static int readPid(File pidFile) { Assert.isTrue(pidFile != null && pidFile.isFile(), String.format( @@ -159,10 +170,10 @@ public abstract class ProcessUtils { return Integer.parseInt(pidValue); } catch (FileNotFoundException e) { - throw new PidUnavailableException(String.format("PID file [%1$s] not found", pidFile), e); + throw new PidUnavailableException(String.format("PID file [%s] not found", pidFile), e); } catch (IOException e) { - throw new PidUnavailableException(String.format("failed to read PID from file [%1$s]", pidFile), e); + throw new PidUnavailableException(String.format("failed to read PID from file [%s]", pidFile), e); } catch (NumberFormatException e) { throw new PidUnavailableException(String.format( @@ -173,6 +184,7 @@ public abstract class ProcessUtils { } } + /* (non-Javadoc) */ @SuppressWarnings("all") public static void writePid(File pidFile, int pid) throws IOException { Assert.isTrue(pidFile != null && (pidFile.isFile() || pidFile.createNewFile()), String.format( @@ -191,20 +203,28 @@ public abstract class ProcessUtils { } } + /* (non-Javadoc) */ protected static class DirectoryPidFileFilter extends PidFileFilter { protected static final DirectoryPidFileFilter INSTANCE = new DirectoryPidFileFilter(); + /** + * @inheritDoc + */ @Override public boolean accept(File path) { return (path != null && (path.isDirectory() || super.accept(path))); } } + /* (non-Javadoc) */ protected static class PidFileFilter implements FileFilter { protected static final PidFileFilter INSTANCE = new PidFileFilter(); + /** + * @inheritDoc + */ @Override public boolean accept(File path) { return (path != null && path.isFile() && path.getName().toLowerCase().endsWith(".pid")); diff --git a/src/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java b/src/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java index 69021418..cbe4c501 100644 --- a/src/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java +++ b/src/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java @@ -18,21 +18,32 @@ package org.springframework.data.gemfire.test.support; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.gemfire.util.ArrayUtils.asArray; import java.io.File; import java.io.IOException; import java.net.Socket; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.geode.cache.server.CacheServer; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.data.gemfire.process.ProcessExecutor; +import org.springframework.data.gemfire.process.ProcessWrapper; +import org.springframework.data.gemfire.util.CollectionUtils; /** * The {@link ClientServerIntegrationTestsSupport} class is a abstract base class encapsulating common functionality * to support the implementation of GemFire client/server tests. * * @author John Blum + * @see java.io.File * @see org.apache.geode.cache.server.CacheServer + * @see org.springframework.context.annotation.AnnotationConfigApplicationContext + * @see org.springframework.data.gemfire.process.ProcessExecutor + * @see org.springframework.data.gemfire.process.ProcessWrapper * @see org.springframework.data.gemfire.test.support.SocketUtils * @see org.springframework.data.gemfire.test.support.ThreadUtils * @since 1.9.0 @@ -41,37 +52,164 @@ import org.apache.geode.cache.server.CacheServer; public class ClientServerIntegrationTestsSupport { protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20); - protected static final long DEFAULT_WAIT_INTERVAL = 500L; + protected static final long DEFAULT_WAIT_INTERVAL = 500L; // milliseconds + + protected static final String DEFAULT_GEMFIRE_LOG_FILE = "gemfire-server.log"; + protected static final String DEFAULT_GEMFIRE_LOG_LEVEL = "config"; + protected static final String DIRECTORY_DELETE_ON_EXIT_PROPERTY = "sdg.directory.delete-on-exit"; + protected static final String GEMFIRE_LOG_FILE_PROPERTY = "gemfire.log.file"; + protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "gemfire.log.level"; + protected static final String PROCESS_RUN_MANUAL_PROPERTY = "sdg.process.run.manual"; + protected static final String SYSTEM_PROPERTIES_LOG_FILE = "system-properties.log"; + protected static final String TEST_GEMFIRE_LOG_LEVEL = "warning"; + + /* (non-Javadoc) */ + protected static String asApplicationName(Class type) { + return type.getSimpleName(); + } + + /* (non-Javadoc) */ + protected static String asDirectoryName(Class type) { + return String.format("%1$s-%2$s", asApplicationName(type), + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-hh-mm-ss"))); + } /* (non-Javadoc) */ protected static File createDirectory(String pathname) { - File directory = new File(pathname); + return createDirectory(new File(pathname)); + } + /* (non-Javadoc) */ + protected static File createDirectory(File directory) { assertThat(directory.isDirectory() || directory.mkdirs()) .as(String.format("Failed to create directory [%s]", directory)).isTrue(); - directory.deleteOnExit(); + if (isDeleteDirectoryOnExit()) { + directory.deleteOnExit(); + } return directory; } + /* (non-Javadoc) */ + protected static boolean isDeleteDirectoryOnExit() { + return Boolean.valueOf(System.getProperty(DIRECTORY_DELETE_ON_EXIT_PROPERTY, Boolean.TRUE.toString())); + } + + /* (non-Javadoc) */ + protected static int intValue(Number number) { + return (number != null ? number.intValue() : 0); + } + + /* (non-Javadoc) */ + protected static String logFile() { + return logFile(DEFAULT_GEMFIRE_LOG_FILE); + } + + /* (non-Javadoc) */ + protected static String logFile(String defaultLogFilePathname) { + return System.getProperty(GEMFIRE_LOG_FILE_PROPERTY, defaultLogFilePathname); + } + + /* (non-Javadoc) */ + protected static String logLevel() { + return logLevel(DEFAULT_GEMFIRE_LOG_LEVEL); + } + + /* (non-Javadoc) */ + protected static String logLevel(String defaultLogLevel) { + return System.getProperty(GEMFIRE_LOG_LEVEL_PROPERTY, defaultLogLevel); + } + + /* (non-Javadoc) */ + protected static void logSystemProperties() throws IOException { + FileUtils.write(new File(SYSTEM_PROPERTIES_LOG_FILE), + String.format("%s", CollectionUtils.toString(System.getProperties()))); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(Class type, String... arguments) throws IOException { + return run(createDirectory(asDirectoryName(type)), type, arguments); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(File workingDirectory, Class type, String... arguments) throws IOException { + return (isProcessRunManual() ? null + : ProcessExecutor.launch(createDirectory(workingDirectory), type, arguments)); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(String classpath, Class type, String... arguments) throws IOException { + return run(createDirectory(asDirectoryName(type)), classpath, type, arguments); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(File workingDirectory, String classpath, Class type, String... arguments) + throws IOException { + + return (isProcessRunManual() ? null + : ProcessExecutor.launch(createDirectory(workingDirectory), classpath, type, arguments)); + } + + /* (non-Javadoc) */ + protected static boolean isProcessRunManual() { + return Boolean.getBoolean(PROCESS_RUN_MANUAL_PROPERTY); + } + + /* (non-Javadoc) */ + protected static AnnotationConfigApplicationContext runSpringApplication(Class annotatedClass, String... args) { + return runSpringApplication(asArray(annotatedClass), args); + } + + /* (non-Javadoc) */ + protected static AnnotationConfigApplicationContext runSpringApplication(Class[] annotatedClasses, + String... args) { + + AnnotationConfigApplicationContext applicationContext = + new AnnotationConfigApplicationContext(annotatedClasses); + + applicationContext.registerShutdownHook(); + + return applicationContext; + } + + /* (non-Javadoc) */ + protected static boolean stop(ProcessWrapper process) { + return stop(process, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + protected static boolean stop(ProcessWrapper process, long duration) { + if (process != null) { + process.stop(duration); + + if (process.isNotRunning() && isDeleteDirectoryOnExit()) { + FileSystemUtils.deleteRecursive(process.getWorkingDirectory()); + } + + return process.isRunning(); + } + + return true; + } + /* (non-Javadoc) */ protected static boolean waitForCacheServerToStart(CacheServer cacheServer) { - return waitForCacheServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION); + return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION); } /* (non-Javadoc) */ protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) { - return waitForCacheServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration); + return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration); } /* (non-Javadoc) */ - protected static boolean waitForCacheServerToStart(String host, int port) { - return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION); + protected static boolean waitForServerToStart(String host, int port) { + return waitForServerToStart(host, port, DEFAULT_WAIT_DURATION); } /* (non-Javadoc) */ - protected static boolean waitForCacheServerToStart(final String host, final int port, long duration) { + protected static boolean waitForServerToStart(final String host, final int port, long duration) { return ThreadUtils.timedWait(duration, DEFAULT_WAIT_INTERVAL, new ThreadUtils.WaitCondition() { AtomicBoolean connected = new AtomicBoolean(false); diff --git a/src/test/java/org/springframework/data/gemfire/test/support/FileSystemUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/FileSystemUtils.java index a5bd8cfd..964d1c8e 100644 --- a/src/test/java/org/springframework/data/gemfire/test/support/FileSystemUtils.java +++ b/src/test/java/org/springframework/data/gemfire/test/support/FileSystemUtils.java @@ -52,7 +52,11 @@ public abstract class FileSystemUtils extends FileUtils { } } - return ((path == null || path.delete()) && success); + return ((!exists(path) || path.delete()) && success); + } + + public static boolean exists(File path) { + return (path != null && path.exists()); } // returns sub-directory just below working directory @@ -124,5 +128,4 @@ public abstract class FileSystemUtils extends FileUtils { return (pathname != null && pathname.isDirectory()); } } - } diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index 9c7903ae..5af5ca74 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -1,16 +1,14 @@ - %d %5p %40.40c:%4L - %m%n - + - + - diff --git a/src/test/resources/org/springframework/data/gemfire/config/AsyncEventQueueNamespaceTest-context.xml b/src/test/resources/org/springframework/data/gemfire/config/xml/AsyncEventQueueNamespaceTest-context.xml similarity index 94% rename from src/test/resources/org/springframework/data/gemfire/config/AsyncEventQueueNamespaceTest-context.xml rename to src/test/resources/org/springframework/data/gemfire/config/xml/AsyncEventQueueNamespaceTest-context.xml index e9ae8f62..d9989cb4 100644 --- a/src/test/resources/org/springframework/data/gemfire/config/AsyncEventQueueNamespaceTest-context.xml +++ b/src/test/resources/org/springframework/data/gemfire/config/xml/AsyncEventQueueNamespaceTest-context.xml @@ -41,7 +41,7 @@ parallel="false" persistent="true"> - diff --git a/src/test/resources/shiro.ini b/src/test/resources/shiro.ini new file mode 100644 index 00000000..2cd25350 --- /dev/null +++ b/src/test/resources/shiro.ini @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# Shiro INI configuration + +# Objects and their properties are defined here, such as the securityManager, Realms and anything else needed +# to build the SecurityManager +[main] + +# The 'users' section is for simple deployments when you only need a small number of statically-defined +# set of User accounts. +# username = password, roleName1, roleName2, …, roleNameN +[users] +root = s3cr3t!, ADMIN, DBA +scientist = w0rk!ng4u, DATA_SCIENTIST +analyst = p@55w0rd, DATA_ANALYST +guest = guest, GUEST + +# The 'roles' section is for simple deployments when you only need a small number of statically-defined roles. +# rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN +[roles] +ADMIN = CLUSTER:MANAGE, CLUSTER:READ, CLUSTER:WRITE +DBA = DATA:MANAGE, DATA:READ, DATA:WRITE +DATA_SCIENTIST = DATA:READ, DATA:WRITE +DATA_ANALYST = DATA:READ +GUEST = NULL + +# The 'urls' section is used for url-based security in web applications. We'll discuss this section +# in the Web documentation +[urls] diff --git a/src/test/resources/shiro.properties b/src/test/resources/shiro.properties new file mode 100644 index 00000000..53ff8446 --- /dev/null +++ b/src/test/resources/shiro.properties @@ -0,0 +1,29 @@ +# Copyright 2016 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. + +# Apache Shiro PropertiesRealm security properties configuration file +# used to configure Apache Geode Security + +# Apache Geode System Users and assigned Roles +user.root = s3c3rt!, ADMIN, DBA +user.scientist = w0rk!ng4u, DATA_SCIENTIST +user.analyst = p@55w0rd, DATA_ANALYST +user.guest = guest, GUEST + +# Apache Geode Roles and granted (Resource) Permissions +role.ADMIN = CLUSTER:MANAGE, CLUSTER:READ, CLUSTER:WRITE +role.DBA = DATA:MANAGE, DATA:READ, DATA:WRITE +role.DATA_SCIENTIST = DATA:READ, DATA:WRITE +role.DATA_ANALYST = DATA:READ +role.GUEST = NULL