Add security support for configuring an Apache Geode/Pivotal GemFire o.a.g.security.SecurityManager as a managed bean in the Spring context.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.data.geode.security.support;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.geode.security.AuthenticationFailedException;
|
||||
import org.apache.geode.security.ResourcePermission;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.gemfire.support.LazyWiringDeclarableSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link SecurityManagerProxy} class is an Apache Geode {@link org.apache.geode.security.SecurityManager}
|
||||
* proxy implementation delegating to a backing {@link org.apache.geode.security.SecurityManager} implementation
|
||||
* which is registered as a managed bean in a Spring context.
|
||||
*
|
||||
* The idea behind this {@link org.apache.geode.security.SecurityManager} is to enable users to be able to configure
|
||||
* and manage the {@code SecurityManager} as a Spring bean. However, Apache Geode/Pivotal GemFire require
|
||||
* the {@link org.apache.geode.security.SecurityManager} to be configured using a System property when launching
|
||||
* Apache Geode Servers with Gfsh, which makes it difficult to "manage" the {@code SecurityManager} instance.
|
||||
*
|
||||
* Therefore, this implementation allows a developer to set the Apache Geode System property using this proxy...
|
||||
*
|
||||
* <code>
|
||||
* gemfire.security-manager=org.springframework.boot.data.geode.security.support.SecurityManagerProxy
|
||||
* </code>
|
||||
*
|
||||
* And then declare and define a bean in the Spring context implementing the
|
||||
* {@link org.apache.geode.security.SecurityManager} interface...
|
||||
*
|
||||
* <code>
|
||||
* @Configuration
|
||||
* class MyApplicationConfiguration {
|
||||
*
|
||||
* @Bean
|
||||
* ExampleSecurityManager exampleSecurityManager(Environment environment) {
|
||||
* return new ExampleSecurityManager(environment);
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
* @see org.springframework.data.gemfire.support.LazyWiringDeclarableSupport
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class SecurityManagerProxy extends LazyWiringDeclarableSupport
|
||||
implements org.apache.geode.security.SecurityManager {
|
||||
|
||||
private static final AtomicReference<SecurityManagerProxy> INSTANCE = new AtomicReference<>();
|
||||
|
||||
private org.apache.geode.security.SecurityManager securityManager;
|
||||
|
||||
/**
|
||||
* Returns a reference to the single {@link SecurityManagerProxy} instance configured by
|
||||
* Apache Geode/Pivotal GemFire in startup.
|
||||
*
|
||||
* @return a reference to the single {@link SecurityManagerProxy} instance.
|
||||
*/
|
||||
public static SecurityManagerProxy getInstance() {
|
||||
|
||||
return Optional.ofNullable(INSTANCE.get())
|
||||
.orElseThrow(() -> newIllegalStateException("SecurityManagerProxy was not configured"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link SecurityManagerProxy}, which will delegate all Apache Geode
|
||||
* security operations to a Spring managed {@link org.apache.geode.security.SecurityManager} bean.
|
||||
*/
|
||||
public SecurityManagerProxy() {
|
||||
|
||||
// TODO remove init() call when GEODE-2083 (https://issues.apache.org/jira/browse/GEODE-2083) is resolved!
|
||||
// NOTE: the init(:Properties) call in the constructor is less than ideal since...
|
||||
// 1) it allows the *this* reference to escape, and...
|
||||
// 2) it is Geode's responsibility to identify Geode Declarable objects and invoke their init(:Properties) method
|
||||
// However, the init(:Properties) method invocation in the constructor is necessary to enable this Proxy to be
|
||||
// identified and auto-wired in a Spring context.
|
||||
|
||||
INSTANCE.compareAndSet(null, this);
|
||||
init(new Properties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a reference to the Apache Geode {@link org.apache.geode.security.SecurityManager} instance
|
||||
* delegated to by this {@link SecurityManagerProxy}.
|
||||
*
|
||||
* @param securityManager reference to the underlying Apache Geode {@link org.apache.geode.security.SecurityManager}
|
||||
* instance delegated to by this {@link SecurityManagerProxy}.
|
||||
* @throws IllegalArgumentException if the {@link org.apache.geode.security.SecurityManager} reference
|
||||
* is {@literal null}.
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
*/
|
||||
@Autowired
|
||||
public void setSecurityManager(org.apache.geode.security.SecurityManager securityManager) {
|
||||
|
||||
Assert.notNull(securityManager, "SecurityManager must not be null");
|
||||
|
||||
this.securityManager = securityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the Apache Geode {@link org.apache.geode.security.SecurityManager} instance
|
||||
* delegated to by this {@link SecurityManagerProxy}.
|
||||
*
|
||||
* @return a reference to the underlying {@link org.apache.geode.security.SecurityManager} instance
|
||||
* delegated to by this {@link SecurityManagerProxy}.
|
||||
* @throws IllegalStateException if the configured {@link org.apache.geode.security.SecurityManager}
|
||||
* was not properly configured.
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
*/
|
||||
protected org.apache.geode.security.SecurityManager getSecurityManager() {
|
||||
|
||||
Assert.state(this.securityManager != null, "No SecurityManager configured");
|
||||
|
||||
return this.securityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object authenticate(Properties properties) throws AuthenticationFailedException {
|
||||
return getSecurityManager().authenticate(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authorize(Object principal, ResourcePermission permission) {
|
||||
return getSecurityManager().authorize(principal, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
getSecurityManager().close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.data.geode.security.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableSecurity;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link SecurityManagerProxy}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.data.gemfire.config.annotation.EnableSecurity
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringRunner
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
public class SecurityManagerProxyIntegrationTests {
|
||||
|
||||
private static final String GEMFIRE_LOG_LEVEL = "error";
|
||||
|
||||
@Autowired
|
||||
private org.apache.geode.security.SecurityManager mockSecurityManager;
|
||||
|
||||
@Test
|
||||
public void securityManagerProxyWasConfiguredWithMockSecurityManager() {
|
||||
assertThat(SecurityManagerProxy.getInstance().getSecurityManager()).isEqualTo(this.mockSecurityManager);
|
||||
}
|
||||
|
||||
@PeerCacheApplication(logLevel = GEMFIRE_LOG_LEVEL, useBeanFactoryLocator = true)
|
||||
@EnableSecurity(securityManagerClassName =
|
||||
"org.springframework.boot.data.geode.security.support.SecurityManagerProxy")
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
org.apache.geode.security.SecurityManager mockSecurityManager(GemFireCache gemfireCache) {
|
||||
return mock(org.apache.geode.security.SecurityManager.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.data.geode.security.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.geode.security.ResourcePermission;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SecurityManagerProxy}
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.security.Principal
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.boot.data.geode.security.support.SecurityManagerProxy
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class SecurityManagerProxyUnitTests {
|
||||
|
||||
@Test
|
||||
public void setAndGetSecurityManager() {
|
||||
|
||||
org.apache.geode.security.SecurityManager mockSecurityManager =
|
||||
mock(org.apache.geode.security.SecurityManager.class);
|
||||
|
||||
SecurityManagerProxy securityManagerProxy = new SecurityManagerProxy();
|
||||
|
||||
securityManagerProxy.setSecurityManager(mockSecurityManager);
|
||||
|
||||
assertThat(securityManagerProxy.getSecurityManager()).isEqualTo(mockSecurityManager);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setSecurityManagerToNullThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new SecurityManagerProxy().setSecurityManager(null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("SecurityManager must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void getSecurityManagerWhenUninitializedThrowsIllegalStateException() {
|
||||
|
||||
try {
|
||||
new SecurityManagerProxy().getSecurityManager();
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("No SecurityManager configured");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateDelegatesToConfiguredSecurityManager() {
|
||||
|
||||
Properties securityProperties = new Properties();
|
||||
|
||||
org.apache.geode.security.SecurityManager mockSecurityManager =
|
||||
mock(org.apache.geode.security.SecurityManager.class);
|
||||
|
||||
when(mockSecurityManager.authenticate(any(Properties.class))).thenReturn("TestUser");
|
||||
|
||||
SecurityManagerProxy securityManagerProxy = new SecurityManagerProxy();
|
||||
|
||||
securityManagerProxy.setSecurityManager(mockSecurityManager);
|
||||
|
||||
assertThat(securityManagerProxy.getSecurityManager()).isEqualTo(mockSecurityManager);
|
||||
assertThat(securityManagerProxy.authenticate(securityProperties)).isEqualTo("TestUser");
|
||||
|
||||
verify(mockSecurityManager, times(1)).authenticate(eq(securityProperties));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeDelegatesToConfiguredSecurityManager() {
|
||||
|
||||
Principal mockPrincipal = mock(Principal.class);
|
||||
|
||||
ResourcePermission resourcePermission =
|
||||
new ResourcePermission(ResourcePermission.Resource.DATA, ResourcePermission.Operation.READ);
|
||||
|
||||
org.apache.geode.security.SecurityManager mockSecurityManager =
|
||||
mock(org.apache.geode.security.SecurityManager.class);
|
||||
|
||||
when(mockSecurityManager.authorize(any(Object.class), any(ResourcePermission.class))).thenReturn(true);
|
||||
|
||||
SecurityManagerProxy securityManagerProxy = new SecurityManagerProxy();
|
||||
|
||||
securityManagerProxy.setSecurityManager(mockSecurityManager);
|
||||
|
||||
assertThat(securityManagerProxy.getSecurityManager()).isEqualTo(mockSecurityManager);
|
||||
assertThat(securityManagerProxy.authorize(mockPrincipal, resourcePermission)).isTrue();
|
||||
|
||||
verify(mockSecurityManager, times(1))
|
||||
.authorize(eq(mockPrincipal), eq(resourcePermission));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeDelegatesToConfiguredSecurityManager() {
|
||||
|
||||
org.apache.geode.security.SecurityManager mockSecurityManager =
|
||||
mock(org.apache.geode.security.SecurityManager.class);
|
||||
|
||||
when(mockSecurityManager.authorize(any(Object.class), any(ResourcePermission.class))).thenReturn(true);
|
||||
|
||||
SecurityManagerProxy securityManagerProxy = new SecurityManagerProxy();
|
||||
|
||||
securityManagerProxy.setSecurityManager(mockSecurityManager);
|
||||
|
||||
assertThat(securityManagerProxy.getSecurityManager()).isEqualTo(mockSecurityManager);
|
||||
|
||||
securityManagerProxy.close();
|
||||
|
||||
verify(mockSecurityManager, times(1)).close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user