#539 - Add spring-data-geode-examples module.

This commit is contained in:
Patrick Johnson
2020-02-06 15:25:47 -08:00
committed by Mark Paluch
parent 08dce4f0f3
commit fa0021cffb
151 changed files with 6712 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security;
/**
* The [Constants] class contains properties and other constants used in the Apache Geode Integrated Security
* framework.
*
* @author John Blum
* @since 1.0.0
*/
public class Constants {
public static final String SECURITY_PASSWORD_PROPERTY = "security-password";
public static final String SECURITY_USERNAME_PROPERTY = "security-username";
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;
import java.io.Serializable;
/**
* A customer used for Lucene examples.
*
* @author Udo Kohlmeyer
* @author Patrick Johnson
*/
@Data
@Region(name = "Customers")
public class Customer implements Serializable {
@Id
private Long id;
private EmailAddress emailAddress;
private String firstName;
private String lastName;
public Customer(Long id, EmailAddress emailAddress, String firstName, String lastName) {
this.id = id;
this.emailAddress = emailAddress;
this.firstName = firstName;
this.lastName = lastName;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security;
import lombok.Data;
import java.io.Serializable;
/**
* Value object to represent email addresses.
*
* @author Udo Kohlmeyer
* @author Patrick Johnson
*/
@Data
public class EmailAddress implements Serializable {
private String value;
public EmailAddress(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security;
import lombok.Data;
import org.apache.geode.security.ResourcePermission;
import org.cp.elements.lang.Identifiable;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
* The [Role] class is an Abstract Data Type (ADT) modeling a role of a user (e.g. Admin).
*
* @author John Blum
* @see Serializable
* @see Comparable
* @see Iterable
* @see ResourcePermission
* @see org.cp.elements.lang.Identifiable
* @since 1.0.0
*/
@Data
public class Role implements Comparable<Role>, Identifiable<String>, Iterable<ResourcePermission>, Serializable {
private String name;
private HashSet<ResourcePermission> permissions = new HashSet<>();
public Role(String name) {
this.name = name;
}
@Override
public String getId() {
return name;
}
@Override
public void setId(String id) {
throw new UnsupportedOperationException("Operation Not Supported");
}
/**
* @inheritDoc
*/
@Override
public int compareTo(Role other) {
return name.compareTo(other.name);
}
/**
* Determines whether this [Role] has been assigned (granted) the given [permission][ResourcePermission].
*
* @param permission [ResourcePermission] to evaluate.
* @return a boolean value indicating whether this [Role] has been assigned (granted)
* the given [permission][ResourcePermission].
* @see ResourcePermission
*/
public boolean hasPermission(ResourcePermission permission) {
return this.permissions.contains(permission);
}
/**
* @inheritDoc
*/
@Override
public Iterator<ResourcePermission> iterator() {
return this.permissions.iterator();
}
/**
* Adds (assigns/grants) all given [persmissions][ResourcePermission] to this [Role].
*
* @param permissions [ResourcePermission]s to assign/grant to this [Role].
* @return this [Role].
* @see ResourcePermission
*/
public Role withPermissions(ResourcePermission... permissions) {
this.permissions.addAll((Arrays.asList(permissions)));
return this;
}
/**
* Adds (assigns/grants) all given [persmissions][ResourcePermission] to this [Role].
*
* @param permissions [ResourcePermission]s to assign/grant to this [Role].
* @return this [Role].
* @see ResourcePermission
*/
public Role withPersmissions(Iterable<ResourcePermission> permissions) {
this.permissions.addAll((Collection<? extends ResourcePermission>) permissions);
return this;
}
/**
* Factory method used to construct a new instance of [Role] initialized with the given name.
*
* @param name [String] indicating the name of the new [Role].
* @return a new [Role] initialized with the given name.
* @see Role
*/
public static Role newRole(String name) {
return new Role(name);
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security;
import lombok.Data;
import org.apache.geode.security.ResourcePermission;
import org.cp.elements.lang.Identifiable;
import java.io.Serializable;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@Data
public class User implements Comparable<User>, Cloneable, Principal, Serializable, Iterable<Role>, Identifiable<String> {
private String name;
private List<Role> roles;
private String credentials = null;
public User(String name, List<Role> roles) {
this.name = name;
this.roles = roles;
}
public User(String name) {
this(name, new ArrayList<Role>());
}
@Override
public void setId(String id) {
throw new UnsupportedOperationException("Operation Not Supported");
}
@Override
public String getId() {
return name;
}
@Override
public Iterator<Role> iterator() {
return roles.iterator();
}
/**
* @inheritDoc
*/
@Override
public Object clone() {
return newUser(name).withCredentials(credentials).withRoles(roles);
}
/**
* @inheritDoc
*/
@Override
public int compareTo(User other) {
return this.getName().compareTo(other.getName());
}
/**
* Determines whether this [User] has been granted (assigned) the given [permission][ResourcePermission].
*
* @param permission [ResourcePermission] to evalute.
* @return a boolean value indicating whether this [User] has been granted (assigned)
* the given [ResourcePermission].
* @see ResourcePermission
*/
public boolean hasPermission(ResourcePermission permission) {
for (Role role : roles) {
if (role.hasPermission(permission)) {
return true;
}
}
return false;
}
/**
* Determines whether this [User] has the specified [Role].
*
* @param role [Role] to evaluate.
* @return a boolean value indicating whether this [User] has the specified [Role].
* @see Role
*/
public boolean hasRole(Role role) {
return roles.contains(role);
}
/**
* Adds the array of [Roles][Role] granting (resource) permissions to this [User].
*
* @param roles array of [Roles][Role] granting (resource) permissions to this [User].
* @return this [User].
* @see Role
*/
public User withRoles(Role... roles) {
this.roles.addAll(Arrays.asList(roles));
return this;
}
public User withRoles(Collection<Role> roles) {
this.roles.addAll(roles);
return this;
}
/**
* Sets this [User&#39;s][User] credentials (e.g. password) to the given value.
*
* @param credentials [String] containing this [User&#39;s][User] credentials (e.g. password).
* @return this [User].
* @see User
*/
public User withCredentials(String credentials) {
this.credentials = credentials;
return this;
}
public static User newUser(String name) {
return new User(name);
}
@Override
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.client;
import example.springdata.geode.client.security.Customer;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findAll();
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.client;
import example.springdata.geode.client.security.Customer;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnableSecurity;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
@Configuration
@EnableSecurity
@EnableGemfireRepositories(basePackageClasses = CustomerRepository.class)
@ClientCacheApplication(name = "SecurityClient", logLevel = "error", pingInterval = 5000L, readTimeout = 15000, retryAttempts = 1)
@EnableEntityDefinedRegions(basePackageClasses = Customer.class, clientRegionShortcut = ClientRegionShortcut.CACHING_PROXY)
public class SecurityEnabledClientConfiguration {
}

View File

@@ -0,0 +1,11 @@
package example.springdata.geode.client.security.server;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.gemfire.config.annotation.EnableSecurity;
@Configuration
@EnableSecurity(shiroIniResourcePath = "shiro.ini")
@Profile("shiro-ini-configuration")
public class ApacheShiroIniConfiguration {
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import example.springdata.geode.client.security.User;
import java.util.HashMap;
import java.util.Map;
/**
* The [CachingSecurityRepository] class caches Security Configuration Meta-Data and is meant to be extended
* by classes that are data store specified (e.g. JDBC/RDBMS, LDAP, etc).
*
* @author John Blum
* @see User
* @see SecurityRepository
* @since 1.0.0
*/
public abstract class CachingSecurityRepository implements SecurityRepository {
private Map<String, User> users = new HashMap<>();
@Override
public Iterable<User> findAll() {
return users.values();
}
@Override
public boolean delete(User user) {
if (user != null) {
return users.remove(user.getName()) != null;
}
return false;
}
@Override
public User save(User user) {
User putUser = users.put(user.getName(), user);
return putUser != null ? putUser : user;
}
}

View File

@@ -0,0 +1,13 @@
package example.springdata.geode.client.security.server;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.gemfire.config.annotation.EnableSecurity;
@Configuration
@EnableSecurity(securityManagerClassName = "example.springdata.geode.client.security.server.SecurityManagerProxy")
@Profile({"default", "geode-security-manager-proxy-configuration"})
public class GeodeIntegratedSecurityProxyConfiguration {
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import example.springdata.geode.client.security.Role;
import example.springdata.geode.client.security.User;
import org.apache.geode.security.ResourcePermission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@Repository
public class JdbcSecurityRepository extends CachingSecurityRepository implements InitializingBean {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private final JdbcTemplate jdbcTemplate;
private static final String ROLES_QUERY = "SELECT name FROM geode_security.roles";
private static final String ROLE_PERMISSIONS_QUERY = ""
+ " SELECT rolePerms.resource, rolePerms.operation, rolePerms.region_name, rolePerms.key_name"
+ " FROM geode_security.roles_permissions rolePerms"
+ " INNER JOIN geode_security.roles roles ON roles.id = rolePerms.role_id "
+ " WHERE roles.name = ?";
private static final String USERS_QUERY = "SELECT name, credentials FROM geode_security.users";
private static final String USER_ROLES_QUERY = ""
+ " SELECT roles.name"
+ " FROM geode_security.roles roles"
+ " INNER JOIN geode_security.users_roles userRoles ON roles.id = userRoles.role_id"
+ " INNER JOIN geode_security.users users ON userRoles.user_id = users.id"
+ " WHERE users.name = ?";
public JdbcSecurityRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void afterPropertiesSet() {
List<Role> roles = this.jdbcTemplate.query(ROLES_QUERY, (resultSet, i) -> Role.newRole(resultSet.getString(1)));
HashMap<String, Role> roleMapping = new HashMap<>(roles.size());
roles.forEach((role) -> {
this.jdbcTemplate.query(ROLE_PERMISSIONS_QUERY, Collections.singleton(role.getName()).toArray(),
(RowMapper<Object>) (resultSet, i) -> role.withPermissions(newResourcePermission(
resultSet.getString(1), resultSet.getString(2),
resultSet.getString(3), resultSet.getString(4))));
roleMapping.put(role.getName(), role);
});
List<User> users = this.jdbcTemplate.query(USERS_QUERY, (resultSet, i) ->
createUser(resultSet.getString(1)).withCredentials(resultSet.getString(2)));
users.forEach((role) -> {
this.jdbcTemplate.query(USER_ROLES_QUERY, Collections.singleton(role.getName()).toArray(),
(RowMapper<Object>) (resultSet, i) -> role.withRoles(roleMapping.get(resultSet.getString(1))));
save(role);
});
logger.debug("Users {}", users);
}
protected ResourcePermission newResourcePermission(String resource, String operation, String region, String key) {
return new ResourcePermission(resource, operation, region, key);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication(scanBasePackageClasses = SecurityEnabledServerConfiguration.class)
public class SecurityEnabledServer {
public static void main(String[] args) {
new SpringApplicationBuilder(SecurityEnabledServer.class)
.web(WebApplicationType.NONE)
.build()
.run(args);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import example.springdata.geode.client.security.Customer;
import org.apache.geode.cache.RegionShortcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnableIndexing;
import org.springframework.data.gemfire.config.annotation.EnableLocator;
import org.springframework.data.gemfire.config.annotation.EnableManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.sql.DataSource;
@Configuration
@EnableLocator
@EnableIndexing
@EnableManager
@Import({ApacheShiroIniConfiguration.class, GeodeIntegratedSecurityProxyConfiguration.class})
@CacheServerApplication(port = 0, logLevel = "error")
@EnableEntityDefinedRegions(basePackageClasses = Customer.class, serverRegionShortcut = RegionShortcut.REPLICATE)
public class SecurityEnabledServerConfiguration {
@Bean
DataSource hsqlDataSource() {
return new EmbeddedDatabaseBuilder()
.setName("geode_security")
.setScriptEncoding("UTF-8")
.setType(EmbeddedDatabaseType.HSQL)
.addScript("sql/geode-security-schema-ddl.sql")
.addScript("sql/define-roles-table-ddl.sql")
.addScript("sql/define-roles-permissions-table-ddl.sql")
.addScript("sql/define-users-table-ddl.sql")
.addScript("sql/define-users-roles-table-ddl.sql")
.addScript("sql/insert-roles-dml.sql")
.addScript("sql/insert-roles-permissions-dml.sql")
.addScript("sql/insert-users-dml.sql")
.addScript("sql/insert-users-roles-dml.sql")
.build();
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.ResourcePermission;
import org.cp.elements.lang.Assert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.gemfire.support.LazyWiringDeclarableSupport;
import java.util.Properties;
/**
* The {@link SecurityManagerProxy} class is a Proxy delegating to an underlying Apache Geode
* {@link org.apache.geode.security.SecurityManager} implementation, that maybe a Spring managed bean
* in a Spring context that may have been configured and auto-wired the Spring container, or possibly
* other managed environment (Cloud or Java EE Server, etc).
*
* @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 org.apache.geode.security.SecurityManager securityManager;
/**
* Constructs an instance of the {@link SecurityManagerProxy}, whick 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.
init(new Properties());
}
/**
* 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, Apache Geode {@link org.apache.geode.security.SecurityManager}
* instance delegated to by this {@link SecurityManagerProxy}.
* @throws IllegalStateException if the configured Apache Geode {@link org.apache.geode.security.SecurityManager}
* was not properly initialized.
* @see org.apache.geode.security.SecurityManager
*/
protected org.apache.geode.security.SecurityManager getSecurityManager() {
Assert.state(this.securityManager != null, "SecurityManager was not properly initialized");
return this.securityManager;
}
/**
* Sets 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 Apache Geode {@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;
}
/**
* @inheritDoc
*/
@Override
public Object authenticate(Properties properties) throws AuthenticationFailedException {
return getSecurityManager().authenticate(properties);
}
/**
* @inheritDoc
*/
@Override
public boolean authorize(Object principal, ResourcePermission permission) {
return getSecurityManager().authorize(principal, permission);
}
/**
* @inheritDoc
*/
@Override
public void close() {
getSecurityManager().close();
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import example.springdata.geode.client.security.Constants;
import org.apache.geode.security.ResourcePermission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.Principal;
import java.util.Properties;
/**
* The [SecurityManagerSupport] class is an Apache Geode [SecurityManager] interface adapter providing
* default implementations of the [SecurityManager] interface operations.
*
* @author John Blum
* @see Principal
* @see ResourcePermission
* @see org.apache.geode.security.SecurityManager
* @since 1.0.0
*/
public abstract class SecurityManagerSupport implements org.apache.geode.security.SecurityManager {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
/* (non-Javadoc)*/
protected String getName(Object principal) {
if (principal instanceof Principal) {
return ((Principal) principal).getName();
} else {
return principal.toString();
}
}
/* (non-Javadoc)*/
protected String getPassword(Properties securityProperties) {
return getPropertyValue(securityProperties, Constants.SECURITY_PASSWORD_PROPERTY);
}
/* (non-Javadoc)*/
protected String getUsername(Properties securityProperties) {
return getPropertyValue(securityProperties, Constants.SECURITY_USERNAME_PROPERTY);
}
/* (non-Javadoc)*/
protected String getPropertyValue(Properties properties, String propertyName) {
return properties.getProperty(propertyName);
}
/* (non-Javadoc)*/
protected void logDebug(String message, Object... args) {
if (logger.isDebugEnabled()) {
logger.debug(message, args);
}
}
/**
* @inheritDoc
*/
@Override
public void init(Properties securityProperties) {
if (logger.isDebugEnabled()) {
logger.debug("Security Properties [{}]", securityProperties);
}
}
/**
* @inheritDoc
*/
@Override
public Object authenticate(Properties securityProperties) {
return null;
}
/**
* @inheritDoc
*/
@Override
public boolean authorize(Object principal, ResourcePermission permission) {
return principal != null;
}
/**
* @inheritDoc
*/
@Override
public void close() {
if (logger.isDebugEnabled()) {
logger.debug("Closing SecurityManager [{}]", this.getClass().getName());
}
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import example.springdata.geode.client.security.Role;
import example.springdata.geode.client.security.User;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The [SecurityRepository] interface is a contract for Data Access Objects (DAO) implementing this interface
* to perform CRUD and query operations on [User] information, pertinent to the security of the system.
*
* @author John Blum
* @author Udo Kohlmeyer
* @see Role
* @see User
* @see org.springframework.stereotype.Repository
* @since 1.0.0
*/
public interface SecurityRepository {
/**
* Finds all [Users][User] of the system.
*
* @return an [Iterable] of [Users][User] of the system.
* @see User
* @see Iterable
*/
Iterable<User> findAll();
/**
* Deletes the given [User] from the system.
*
* @param user [User] to delete.
* @return a boolean value indicating if the [User] was deleted successfully.
* @see User
*/
boolean delete(User user);
/**
* Records (persist) the information (state) of the [User].
*
* @param user [User] to store.
* @return the [User].
* @see User
*/
User save(User user);
/* (non-Javadoc) */
default int count() {
int count = 0;
Iterable<User> users = findAll();
for (User user : users) {
count++;
}
return count;
}
/* (non-Javadoc) */
default User createUser(String username, Role... roles) {
return save(User.newUser(username).withRoles(roles));
}
/* (non-Javadoc) */
default boolean delete(String username) {
User user = findBy(username);
if (user != null) {
return delete(user);
}
return false;
}
/* (non-Javadoc) */
default boolean deleteAll(String... username) {
return deleteAll(findAll(username));
}
/* (non-Javadoc) */
default boolean deleteAll(User... users) {
return deleteAll(Arrays.asList(users));
}
/* (non-Javadoc) */
default boolean deleteAll(Iterable<User> users) {
for (User user : users) {
if (!delete(user)) {
return false;
}
}
return true;
}
/* (non-Javadoc) */
default boolean deleteAll() {
return deleteAll(findAll());
}
/* (non-Javadoc) */
default boolean exists(String username) {
return findBy(username) != null;
}
/* (non-Javadoc) */
default Iterable<User> findAll(String... username) {
return findAll(Arrays.asList(username));
}
/* (non-Javadoc) */
default Iterable<User> findAll(Iterable<String> username) {
List<User> all = new ArrayList<>();
username.forEach(u -> {
if (u != null) {
all.add(findBy(u));
}
});
return all;
}
/* (non-Javadoc) */
default User findBy(String username) {
for (User user : findAll()) {
if (user.getName().equals(username)) {
return user;
}
}
return null;
}
/* (non-Javadoc) */
default Iterable<User> saveAll(User... users) {
return saveAll(Arrays.asList(users));
}
/* (non-Javadoc) */
default Iterable<User> saveAll(Iterable<User> users) {
List<User> saved = new ArrayList<>();
users.forEach(user -> saved.add(save(user)));
return saved;
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.server;
import example.springdata.geode.client.security.Role;
import example.springdata.geode.client.security.User;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.ResourcePermission;
import org.springframework.stereotype.Service;
import java.util.Properties;
/**
* The [SimpleSecurityManager] class is an example Apache Geode [SecurityManager] provider implementation
* used to secure Apache Geode.
*
* @author John Blum
* @see SecurityManagerSupport
* @see Role
* @see User
* @see SecurityRepository
* @see ResourcePermission
* @see org.apache.geode.security.SecurityManager
* @see org.springframework.stereotype.Service
* @since 1.0.0
*/
@Service
public class SimpleSecurityManager extends SecurityManagerSupport {
protected SecurityRepository securityRepository;
public SimpleSecurityManager(SecurityRepository securityRepository) {
this.securityRepository = securityRepository;
}
/**
* @inheritDoc
*/
@Override
public Object authenticate(Properties securityProperties) {
String username = getUsername(securityProperties);
String password = getPassword(securityProperties);
logDebug("User with name [{}] is attempting to login with password [{}]", username, password);
User user = securityRepository.findBy(username);
if (isNotAuthentic(user, password)) {
throw new AuthenticationFailedException(String.format("Failed to authenticate user [%s]", username));
}
return user;
}
/* (non-Javadoc) */
protected boolean isAuthentic(User user, String credentials) {
return user != null && user.getCredentials().equals(credentials);
}
/* (non-Javadoc) */
protected boolean isNotAuthentic(User user, String credentials) {
return !isAuthentic(user, credentials);
}
/**
* @inheritDoc
*/
@Override
public boolean authorize(Object principal, ResourcePermission permission) {
logDebug("Principal [{}] is requesting access to a Resource {} with the required Permission [{}]",
principal, permission.getResource(), permission);
return isAuthorized(principal, permission);
}
/* (non-Javadoc) */
protected boolean isAuthorized(Object principal, ResourcePermission permission) {
User user = resolveUser(principal);
return user != null && isAuthorized(user, permission);
}
/* (non-Javadoc) */
protected User resolveUser(Object principal) {
if (principal instanceof User) {
return (User) principal;
} else {
return securityRepository.findBy(getName(principal));
}
}
/* (non-Javadoc) */
protected boolean isAuthorized(User user, ResourcePermission requiredPermission) {
if (!user.hasPermission(requiredPermission)) {
for (Role role : user.getRoles()) {
for (ResourcePermission userPermission : role.getPermissions()) {
if (isPermitted(userPermission, requiredPermission)) {
return true;
}
}
}
return false;
}
return true;
}
/* (non-Javadoc) */
protected boolean isPermitted(ResourcePermission userPermission, ResourcePermission resourcePermission) {
return userPermission.implies(resourcePermission);
}
}

View File

@@ -0,0 +1,2 @@
spring.data.gemfire.security.username=scientist
spring.data.gemfire.security.password=w0rk!ng4u

View File

@@ -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]

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS geode_security.roles_permissions (
role_id INTEGER REFERENCES geode_security.roles(id),
resource VARCHAR(32) NOT NUll,
operation VARCHAR(32) NOT NULL,
region_name VARCHAR(255),
key_name VARCHAR(255)
);

View File

@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS geode_security.roles (
id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1),
name VARCHAR(32) NOT NULL
);

View File

@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS geode_security.users_roles (
user_id INTEGER REFERENCES geode_security.users(id),
role_id INTEGER REFERENCES geode_security.roles(id)
);

View File

@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS geode_security.users (
id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1),
name VARCHAR(255) NOT NULL,
credentials VARCHAR(255)
);

View File

@@ -0,0 +1 @@
CREATE SCHEMA geode_security

View File

@@ -0,0 +1,5 @@
INSERT INTO geode_security.roles (name) VALUES ('ADMIN');
INSERT INTO geode_security.roles (name) VALUES ('DBA');
INSERT INTO geode_security.roles (name) VALUES ('DATA_SCIENTIST');
INSERT INTO geode_security.roles (name) VALUES ('DATA_ANALYST');
INSERT INTO geode_security.roles (name) VALUES ('GUEST');

View File

@@ -0,0 +1,11 @@
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'ADMIN'), 'CLUSTER', 'MANAGE');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'ADMIN'), 'CLUSTER', 'READ');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'ADMIN'), 'CLUSTER', 'WRITE');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'DBA'), 'DATA', 'MANAGE');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'DBA'), 'DATA', 'READ');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'DBA'), 'DATA', 'WRITE');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'DATA_SCIENTIST'), 'DATA', 'READ');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'DATA_SCIENTIST'), 'DATA', 'WRITE');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'DATA_ANALYST'), 'DATA', 'READ');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'GUEST'), 'CLUSTER', 'NULL');
INSERT INTO geode_security.roles_permissions (role_id, resource, operation) VALUES ((SELECT id FROM geode_security.roles WHERE name = 'GUEST'), 'DATA', 'NULL');

View File

@@ -0,0 +1,4 @@
INSERT INTO geode_security.users (name, credentials) VALUES ('root', 's3cr3t!');
INSERT INTO geode_security.users (name, credentials) VALUES ('scientist', 'w0rk!ng4u');
INSERT INTO geode_security.users (name, credentials) VALUES ('analyst', 'p@55w0rd');
INSERT INTO geode_security.users (name, credentials) VALUES ('guest', 'guest');

View File

@@ -0,0 +1,5 @@
INSERT INTO geode_security.users_roles (user_id, role_id) VALUES ((SELECT id FROM geode_security.users WHERE name = 'root'), (SELECT id FROM geode_security.roles WHERE name = 'ADMIN'));
INSERT INTO geode_security.users_roles (user_id, role_id) VALUES ((SELECT id FROM geode_security.users WHERE name = 'root'), (SELECT id FROM geode_security.roles WHERE name = 'DBA'));
INSERT INTO geode_security.users_roles (user_id, role_id) VALUES ((SELECT id FROM geode_security.users WHERE name = 'scientist'), (SELECT id FROM geode_security.roles WHERE name = 'DATA_SCIENTIST'));
INSERT INTO geode_security.users_roles (user_id, role_id) VALUES ((SELECT id FROM geode_security.users WHERE name = 'analyst'), (SELECT id FROM geode_security.roles WHERE name = 'DATA_ANALYST'));
INSERT INTO geode_security.users_roles (user_id, role_id) VALUES ((SELECT id FROM geode_security.users WHERE name = 'guest'), (SELECT id FROM geode_security.roles WHERE name = 'GUEST'));

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.client;
import example.springdata.geode.client.security.Customer;
import example.springdata.geode.client.security.EmailAddress;
import example.springdata.geode.client.security.server.SecurityEnabledServer;
import org.apache.geode.cache.Region;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = SecurityEnabledClientConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SecurityEnabledClientShiroTests extends ForkingClientServerIntegrationTestsSupport {
@Autowired
private CustomerRepository customerRepository;
@Resource(name = "Customers")
private Region<Long, Customer> customers;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@BeforeClass
public static void setup() throws IOException {
startGemFireServer(SecurityEnabledServer.class, "-Dspring.profiles.active=shiro-ini-configuration");
}
@Test
public void securityWasConfiguredCorrectly() {
logger.info("Inserting 3 entries for keys: 1, 2, 3");
Customer john = new Customer(1L, new EmailAddress("2@2.com"), "John", "Smith");
Customer frank = new Customer(2L, new EmailAddress("3@3.com"), "Frank", "Lamport");
Customer jude = new Customer(3L, new EmailAddress("5@5.com"), "Jude", "Simmons");
customerRepository.save(john);
customerRepository.save(frank);
customerRepository.save(jude);
assertThat(customers.keySetOnServer().size()).isEqualTo(3);
logger.info("Customers saved on server:");
List<Customer> customerList = customerRepository.findAll();
assertThat(customerList.size()).isEqualTo(3);
assertThat(customerList.contains(john)).isTrue();
assertThat(customerList.contains(frank)).isTrue();
assertThat(customerList.contains(jude)).isTrue();
customerList.forEach(customer -> logger.info("\t Entry: \n \t\t " + customer));
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2020 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
*
* https://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 example.springdata.geode.client.security.client;
import example.springdata.geode.client.security.Customer;
import example.springdata.geode.client.security.EmailAddress;
import example.springdata.geode.client.security.server.SecurityEnabledServer;
import org.apache.geode.cache.Region;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = SecurityEnabledClientConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SecurityEnabledClientTests extends ForkingClientServerIntegrationTestsSupport {
@Autowired
private CustomerRepository customerRepository;
@Resource(name = "Customers")
private Region<Long, Customer> customers;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@BeforeClass
public static void setup() throws IOException {
startGemFireServer(SecurityEnabledServer.class);
}
@Test
public void SecurityWasConfiguredCorrectly() {
logger.info("Inserting 3 entries for keys: 1, 2, 3");
Customer john = new Customer(1L, new EmailAddress("2@2.com"), "John", "Smith");
Customer frank = new Customer(2L, new EmailAddress("3@3.com"), "Frank", "Lamport");
Customer jude = new Customer(3L, new EmailAddress("5@5.com"), "Jude", "Simmons");
customerRepository.save(john);
customerRepository.save(frank);
customerRepository.save(jude);
assertThat(customers.keySetOnServer().size()).isEqualTo(3);
logger.info("Customers saved on server:");
List<Customer> customerList = customerRepository.findAll();
assertThat(customerList.size()).isEqualTo(3);
assertThat(customerList.contains(john)).isTrue();
assertThat(customerList.contains(frank)).isTrue();
assertThat(customerList.contains(jude)).isTrue();
customerList.forEach(customer -> logger.info("\t Entry: \n \t\t " + customer));
}
}

View File

@@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="error">
<appender-ref ref="STDOUT"/>
</root>
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
</configuration>