DATAGEODE-24 - Enable the AutoConfiguredAuthenticationConfigurationIntegrationTests.clientAuthenticatesWithServer() test case.

This commit is contained in:
John Blum
2017-07-24 18:18:52 -07:00
parent b6f06b0a23
commit ca0776d977
7 changed files with 158 additions and 137 deletions

View File

@@ -18,9 +18,6 @@
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;
@@ -35,7 +32,6 @@ 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;
@@ -43,17 +39,12 @@ 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.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;
@@ -63,6 +54,7 @@ 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.config.annotation.support.AbstractAuthInitialize;
import org.springframework.data.gemfire.process.ProcessWrapper;
import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport;
import org.springframework.data.gemfire.util.CollectionUtils;
@@ -80,10 +72,15 @@ import lombok.RequiredArgsConstructor;
* Abstract base test class for implementing Apache Geode Integrated Security Integration Tests.
*
* @author John Blum
* @see java.security.Principal
* @see java.util.Properties
* @see org.junit.FixMethodOrder
* @see org.junit.Test
* @see lombok
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAuthInitialize
* @see org.springframework.data.gemfire.process.ProcessWrapper
* @see org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport
* @since 1.0.0
*/
@@ -106,6 +103,7 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
@BeforeClass
public static void runGeodeServer() throws IOException {
String geodeSecurityProfile = System.getProperty(GEODE_SECURITY_PROFILE_PROPERTY);
if (StringUtils.hasText(geodeSecurityProfile)) {
@@ -114,13 +112,17 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
}
protected static ProcessWrapper runGeodeServer(String geodeSecurityProfile) throws IOException {
Assert.hasText(geodeSecurityProfile, String.format("The '%s' System property must be set",
Assert.hasText(geodeSecurityProfile, String.format("[%s] System property is required",
GEODE_SECURITY_PROFILE_PROPERTY));
String debugEndpoint = Boolean.getBoolean(DEBUGGING_ENABLED_PROPERTY) ? DEBUG_ENDPOINT : null;
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));
String.format("-Dspring.profiles.active=apache-geode-server,%s", geodeSecurityProfile),
debugEndpoint);
waitForServerToStart(CACHE_SERVER_HOST, CACHE_SERVER_PORT);
@@ -133,9 +135,6 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
stop(geodeServerProcess);
}
@Rule
public ExpectedException exception = ExpectedException.none();
@Resource(name = "Echo")
private Region<String, String> echo;
@@ -147,45 +146,24 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
assertThat(echo.get("two")).isEqualTo("four");
}
@Test
@Test(expected = NotAuthorizedException.class)
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 {
try {
assertThat(echo.get("one")).isEqualTo("one");
echo.put("two", "four");
}
catch (ServerOperationException expected) {
assertThat(expected).hasMessageContaining("analyst not authorized for DATA:WRITE:Echo:two");
assertThat(expected).hasCauseInstanceOf(NotAuthorizedException.class);
/**
* @inheritDoc
*/
@Override
public Properties getCredentials(Properties securityProperties, DistributedMember server, boolean isPeer)
throws AuthenticationFailedException {
return getCredentials(securityProperties);
throw (NotAuthorizedException) expected.getCause();
}
/**
* @inheritDoc
*/
@Override
public void close() {
finally {
assertThat(echo).doesNotContainKey("two");
}
}
public static class GeodeClientAuthInitialize extends AuthInitializeSupport {
public static class GeodeClientAuthInitialize extends AbstractAuthInitialize {
protected static final User ANALYST = User.newUser("analyst").with("p@55w0rd");
protected static final User SCIENTIST = User.newUser("scientist").with("w0rk!ng4u");
@@ -203,11 +181,10 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
this.user = user;
}
/**
* @inheritDoc
*/
/* (non-Javadoc) */
@Override
public Properties getCredentials(Properties securityProperties) {
protected Properties doGetCredentials(Properties securityProperties) {
User user = getUser();
return new PropertiesBuilder()
@@ -226,19 +203,23 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
*/
@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")
@EnableAuth(clientAuthenticationInitializer =
"org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests$GeodeClientAuthInitialize.create")
@Profile("apache-geode-client")
static class GeodeClientConfiguration {
@Bean("Echo")
ClientRegionFactoryBean<String, String> echoRegion(GemFireCache gemfireCache) {
ClientRegionFactoryBean<String, String> echoRegion = new ClientRegionFactoryBean<>();
echoRegion.setCache(gemfireCache);
@@ -268,6 +249,7 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
@Bean("Echo")
LocalRegionFactoryBean<String, String> echoRegion(GemFireCache gemfireCache) {
LocalRegionFactoryBean<String, String> echoRegion = new LocalRegionFactoryBean<>();
echoRegion.setCache(gemfireCache);
@@ -279,7 +261,9 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
}
CacheLoader<String, String> echoCacheLoader() {
return new CacheLoader<String, String>() {
@Override
public String load(LoaderHelper<String, String> helper) throws CacheLoaderException {
return helper.getKey();
@@ -314,6 +298,7 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
/* (non-Javadoc) */
public boolean hasPermission(ResourcePermission permission) {
for (Role role : this) {
if (role.hasPermission(permission)) {
return true;
@@ -346,13 +331,17 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
/* (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;
}
}
@@ -370,6 +359,7 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
/* (non-Javadoc) */
public boolean hasPermission(ResourcePermission permission) {
for (ResourcePermission thisPermission : this) {
if (thisPermission.implies(permission)) {
return true;
@@ -383,6 +373,7 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
* @inheritDoc
*/
@Override
@SuppressWarnings("all")
public Iterator<ResourcePermission> iterator() {
return Collections.unmodifiableSet(this.permissions).iterator();
}
@@ -397,7 +388,9 @@ public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServer
/* (non-Javadoc) */
public Role with(ResourcePermission... permissions) {
Collections.addAll(this.permissions, permissions);
return this;
}
}

View File

@@ -58,7 +58,8 @@ public class ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests extends Abs
}
@Configuration
@EnableSecurity(securityManagerClassName = "org.springframework.data.gemfire.config.annotation.ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests$TestGeodeSecurityManager")
@EnableSecurity(securityManagerClassName =
"org.springframework.data.gemfire.config.annotation.ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests$TestGeodeSecurityManager")
@Profile(GEODE_SECURITY_MANAGER_PROPERTY_CONFIGURATION_PROFILE)
public static class ApacheGeodeSecurityManagerConfiguration {
}
@@ -87,28 +88,11 @@ public class ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests extends Abs
Set<User> 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();
return users.stream().filter((user) -> user.getName().equals(username)).findFirst().orElse(null);
}
}
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 {
public static class TestGeodeSecurityManager implements org.apache.geode.security.SecurityManager {
private final GeodeSecurityRepository securityRepository;
@@ -121,6 +105,7 @@ public class ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests extends Abs
*/
@Override
public Object authenticate(Properties credentials) throws AuthenticationFailedException {
String username = credentials.getProperty(SECURITY_USERNAME_PROPERTY);
String password = credentials.getProperty(SECURITY_PASSWORD_PROPERTY);

View File

@@ -31,7 +31,6 @@ 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;
@@ -50,6 +49,7 @@ import org.springframework.mock.env.MockPropertySource;
* @author John Blum
* @since 1.0.0
*/
@SuppressWarnings("unused")
public class AutoConfiguredAuthenticationConfigurationIntegrationTests extends ClientServerIntegrationTestsSupport {
private static final int PORT = 42124;
@@ -64,7 +64,8 @@ public class AutoConfiguredAuthenticationConfigurationIntegrationTests extends C
gemfireServerProcess = run(TestGemFireServerConfiguration.class, String.format("-Dgemfire.name=%1$s",
asApplicationName(AutoConfiguredAuthenticationConfigurationIntegrationTests.class)));
waitForServerToStart("localhost", PORT);
Optional.ofNullable(gemfireServerProcess)
.ifPresent(server -> waitForServerToStart("localhost", PORT));
}
@AfterClass
@@ -78,7 +79,7 @@ public class AutoConfiguredAuthenticationConfigurationIntegrationTests extends C
}
private ConfigurableApplicationContext newApplicationContext(PropertySource<?> testPropertySource,
Class<?>... annotatedClasses) {
Class<?>... annotatedClasses) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
@@ -94,9 +95,8 @@ public class AutoConfiguredAuthenticationConfigurationIntegrationTests extends C
}
@Test
@Ignore
@SuppressWarnings("unchecked")
public void clientAuthenticatedWithServer() {
public void clientAuthenticatesWithServer() {
MockPropertySource testPropertySource = new MockPropertySource()
.withProperty("spring.data.gemfire.security.username", SECURITY_USERNAME)
@@ -109,11 +109,13 @@ public class AutoConfiguredAuthenticationConfigurationIntegrationTests extends C
Region<Object, Object> echo = this.applicationContext.getBean("Echo", Region.class);
assertThat(echo.get("Hello")).isEqualTo("Hello");
assertThat(echo.get("TEST")).isEqualTo("TEST");
assertThat(echo.get("Good-Bye")).isEqualTo("Good-Bye");
}
@EnableSecurity
@ClientCacheApplication(servers = @ClientCacheApplication.Server(port = PORT))
@ClientCacheApplication(logLevel = TEST_GEMFIRE_LOG_LEVEL, servers = @ClientCacheApplication.Server(port = PORT))
static class TestGemFireClientConfiguration {
@Bean("Echo")
@@ -129,16 +131,12 @@ public class AutoConfiguredAuthenticationConfigurationIntegrationTests extends C
}
}
@CacheServerApplication(port = PORT)
@CacheServerApplication(logLevel = TEST_GEMFIRE_LOG_LEVEL, 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();
runSpringApplication(TestGemFireServerConfiguration.class, args);
}
@Bean("Echo")

View File

@@ -16,16 +16,19 @@
package org.springframework.data.gemfire.config.annotation;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.config.annotation.TestSecurityManager.TestPrincipal.newPrincipal;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
import java.security.Principal;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
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;
import org.springframework.util.StringUtils;
/**
* The {@link TestSecurityManager} class is an Apache Geode / Pivotal GemFire
@@ -34,17 +37,18 @@ import org.springframework.util.ObjectUtils;
* @author John Blum
* @see java.security.Principal
* @see java.util.Properties
* @see javax.security.auth.Subject
* @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 = "testUser";
public static final String SECURITY_PASSWORD = "&t35t9@55w0rd!";
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<String, String> authorizedUsers;
public TestSecurityManager() {
@@ -52,34 +56,49 @@ public final class TestSecurityManager implements org.apache.geode.security.Secu
this.authorizedUsers.putIfAbsent(SECURITY_USERNAME, SECURITY_PASSWORD);
}
protected Map<String, String> getAuthorizedUsers() {
return Collections.unmodifiableMap(this.authorizedUsers);
}
@Override
public Object authenticate(Properties properties) throws AuthenticationFailedException {
public Object authenticate(Properties credentials) throws AuthenticationFailedException {
String username = properties.getProperty(SECURITY_USERNAME_PROPERTY);
String password = properties.getProperty(SECURITY_PASSWORD_PROPERTY);
String username = credentials.getProperty(SECURITY_USERNAME_PROPERTY);
String password = credentials.getProperty(SECURITY_PASSWORD_PROPERTY);
return validateIdentified(isIdentified(username, password) ? newPrincipal(username) : null, username);
return Optional.ofNullable(identify(username, password)).orElseThrow(() ->
new AuthenticationFailedException(String.format("User [%s] is not authorized", username)));
}
private Principal identify(String username, String password) {
return (isIdentified(username, password) ? newPrincipal(username) : null);
}
private boolean isIdentified(String username, String password) {
return ObjectUtils.nullSafeEquals(this.authorizedUsers.get(String.valueOf(username)), password);
return Optional.ofNullable(username)
.filter(StringUtils::hasText)
.map(user -> getAuthorizedUsers().get(user))
.map(userPassword -> userPassword.equals(password))
.orElse(false);
}
private Principal newPrincipal(String username) {
public static final class TestPrincipal implements java.security.Principal, java.io.Serializable {
Principal mockPrincipal = mock(Principal.class, username);
private final String name;
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));
public static TestPrincipal newPrincipal(String username) {
return new TestPrincipal(username);
}
return principal;
public TestPrincipal(String name) {
this.name = Optional.ofNullable(name).filter(StringUtils::hasText)
.orElseThrow(() -> newIllegalArgumentException("Name is required"));
}
@Override
public String getName() {
return this.name;
}
}
}

View File

@@ -27,6 +27,7 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -58,13 +59,15 @@ public class ClientServerIntegrationTestsSupport {
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(30);
protected static final long DEFAULT_WAIT_INTERVAL = 500L; // milliseconds
protected static final String DEBUG_ENDPOINT = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005";
protected static final String DEBUGGING_ENABLED_PROPERTY = "spring.data.gemfire.debugging.enabled";
protected static final String DEFAULT_HOSTNAME = "localhost";
protected static final String DIRECTORY_DELETE_ON_EXIT_PROPERTY = "spring.data.gemfire.directory.delete-on-exit";
protected static final String GEMFIRE_CACHE_SERVER_PORT_PROPERTY = "spring.data.gemfire.cache.server.port";
protected static final String GEMFIRE_LOG_FILE = "gemfire-server.log";
protected static final String GEMFIRE_LOG_FILE_PROPERTY = "spring.data.gemfire.gemfire.log.file";
protected static final String GEMFIRE_LOG_FILE_PROPERTY = "spring.data.gemfire.log.file";
protected static final String GEMFIRE_LOG_LEVEL = "warning";
protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "spring.data.gemfire.gemfire.log.level";
protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "spring.data.gemfire.log.level";
protected static final String PROCESS_RUN_MANUAL_PROPERTY = "spring.data.gemfire.process.run-manual";
protected static final String SYSTEM_PROPERTIES_LOG_FILE = "system-properties.log";
protected static final String TEST_GEMFIRE_LOG_LEVEL = "warning";
@@ -87,6 +90,7 @@ public class ClientServerIntegrationTestsSupport {
/* (non-Javadoc) */
protected static File createDirectory(File directory) {
assertThat(directory.isDirectory() || directory.mkdirs())
.as(String.format("Failed to create directory [%s]", directory)).isTrue();
@@ -99,6 +103,7 @@ public class ClientServerIntegrationTestsSupport {
/* (non-Javadoc) */
protected static int findAvailablePort() throws IOException {
ServerSocket serverSocket = null;
try {
@@ -232,17 +237,19 @@ public class ClientServerIntegrationTestsSupport {
/* (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 Optional.ofNullable(process)
.map(it -> {
return process.isRunning();
}
it.stop(duration);
return true;
if (it.isNotRunning() && isDeleteDirectoryOnExit()) {
FileSystemUtils.deleteRecursive(it.getWorkingDirectory());
}
return it.isRunning();
})
.orElse(true);
}
/* (non-Javadoc) */
@@ -262,10 +269,13 @@ public class ClientServerIntegrationTestsSupport {
/* (non-Javadoc) */
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);
public boolean waiting() {
Socket socket = null;
try {
@@ -291,6 +301,7 @@ public class ClientServerIntegrationTestsSupport {
@SuppressWarnings("all")
protected static boolean waitOn(Condition condition, long duration) {
long timeout = (System.currentTimeMillis() + duration);
try {