/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security.password;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.security.GeoServerSecurityManager;
import org.geotools.data.DataAccessFactory;
import org.geotools.data.DataAccessFactory.Param;
import org.geotools.util.logging.Logging;
/**
* Helper class for encryption of passwords in connection parameters for {@link StoreInfo} objects.
* <p>
* This class will encrypt any password parameter from {@link StoreInfo#getConnectionParameters()}.
* </p>
*
* @author christian
*/
public class ConfigurationPasswordEncryptionHelper {
static protected Logger LOGGER = Logging.getLogger("org.geoserver.security");
/**
* cache of datastore factory class to fields to encrypt
*/
static protected Map<Class<? extends DataAccessFactory>, Set<String>> CACHE =
new HashMap<Class<? extends DataAccessFactory>, Set<String>>();
GeoServerSecurityManager securityManager;
public ConfigurationPasswordEncryptionHelper(GeoServerSecurityManager securityManager) {
this.securityManager = securityManager;
}
public Catalog getCatalog() {
// JD: this class gets called during catalog initialization when reading store instances that
// potentially have encrypted parameters, so we have to be careful about how we access the
// catalog, raw catalog directly to avoid triggering the initialization of the secure
// catalog as we are reading the raw catalog contents (this could for instance cause a rule
//to be ignored since a workspace has not been read)
return (Catalog) GeoServerExtensions.bean("rawCatalog");
}
/**
* Determines the fields in {@link StoreInfo#getConnectionParameters()} that require encryption
* for this type of store object.
*/
public Set<String> getEncryptedFields(StoreInfo info) {
if (!(info instanceof DataStoreInfo)) {
//only datastores supposed at this time, TODO: fix this
return Collections.emptySet();
}
//find this store object data access factory
DataAccessFactory factory;
try {
factory = getCatalog().getResourcePool().getDataStoreFactory((DataStoreInfo) info);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Error looking up factory for store : " + info + ". Unable to " +
"encrypt connection parameters.", e);
return Collections.emptySet();
}
if (factory == null) {
LOGGER.warning("Could not find factory for store : " + info + ". Unable to encrypt " +
"connection parameters.");
return Collections.emptySet();
}
//if factory returns no info no need to continue
if (factory.getParametersInfo() == null) {
return Collections.emptySet();
}
Set<String> toEncrypt = CACHE.get(factory.getClass());
if (toEncrypt!=null) {
return toEncrypt;
}
synchronized (CACHE) {
toEncrypt = CACHE.get(info.getClass());
if (toEncrypt!=null) {
return toEncrypt;
}
toEncrypt = Collections.emptySet();
if (info != null && info.getConnectionParameters() != null) {
toEncrypt = new HashSet<String>(3);
for (Param p : factory.getParametersInfo()) {
if (p.isPassword()) {
toEncrypt.add(p.getName());
}
}
}
CACHE.put(factory.getClass(), toEncrypt);
}
return toEncrypt;
}
/**
* Encrypts a parameter value.
* <p>
* If no encoder is configured then the value is returned as is.
* </p>
*/
public String encode(String value) {
String encoderName = securityManager.getSecurityConfig().getConfigPasswordEncrypterName();
if (encoderName != null) {
GeoServerPasswordEncoder pwEncoder = securityManager.loadPasswordEncoder(encoderName);
if (pwEncoder != null) {
String prefix = pwEncoder.getPrefix();
if (value.startsWith(prefix+GeoServerPasswordEncoder.PREFIX_DELIMTER)) {
throw new RuntimeException("Cannot encode a password with prefix: "+
prefix+GeoServerPasswordEncoder.PREFIX_DELIMTER);
}
value = pwEncoder.encodePassword(value, null);
}
}
else {
LOGGER.warning("Encryption disabled, no password encoder set");
}
return value;
}
/**
* Decrypts previously encrypted store connection parameters.
*/
public void decode(StoreInfo info) {
List<GeoServerPasswordEncoder> encoders =
securityManager.loadPasswordEncoders(null,true,null);
Set<String> encryptedFields = getEncryptedFields(info);
if (info.getConnectionParameters() !=null) {
for (String key : info.getConnectionParameters().keySet()) {
if (encryptedFields.contains(key)) {
String value = (String)info.getConnectionParameters().get(key);
if (value!=null) {
info.getConnectionParameters().put(key, decode(value, encoders));
}
}
}
}
}
/**
* Decrypts a previously encrypted value.
*/
public String decode(String value) {
return decode(value,
securityManager.loadPasswordEncoders(null,true,null));
}
String decode(String value, List<GeoServerPasswordEncoder> encoders) {
for (GeoServerPasswordEncoder encoder : encoders) {
if (encoder.isReversible()==false)
continue; // should not happen
if (encoder.isResponsibleForEncoding(value)) {
return encoder.decode(value);
}
}
return value;
}
}