/*
* RHQ Management Platform
* Copyright (C) 2005-2012 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.core.db.upgrade;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.rhq.core.db.DatabaseType;
import org.rhq.core.util.obfuscation.Obfuscator;
/**
* Abstract base class for obfuscating the passwords in the database.
* Because configurations and configuration definitions are only loosely coupled to
* the entities, this class provides the upgrade functionality only from the point
* where the configuration - configuration definition pairs are known.
*
* It is the subclasses responsibility to provide those pairs based on the entity type
* the subclass handles.
*
* @author Lukas Krejci
*/
public abstract class AbstractConfigurationObfuscationUpgradeTask implements DatabaseUpgradeTask {
protected Connection connection;
protected DatabaseType databaseType;
enum PropertyType {
MAP,
LIST,
SIMPLE,
OBFUSCATED;
static PropertyType fromName(String name) {
if ("map".equals(name)) {
return MAP;
} else if ("list".equals(name)) {
return LIST;
} else if ("property".equals(name)) {
return SIMPLE;
} else if ("obfuscated".equals(name)) {
return OBFUSCATED;
} else {
return null;
}
}
}
enum PropertySimpleType {
VARIOUS,
PASSWORD;
static PropertySimpleType fromName(String name) {
if ("PASSWORD".equals(name)) {
return PASSWORD;
} else {
return VARIOUS;
}
}
}
class PropertyDefinition {
int id;
String name;
PropertyType type;
PropertySimpleType simpleType;
List<PropertyDefinition> getChildDefinitions() throws SQLException {
if (type == PropertyType.SIMPLE || type == PropertyType.OBFUSCATED) {
return Collections.emptyList();
}
String sql = "SELECT id, name, dtype, simple_type FROM rhq_config_prop_def WHERE ";
switch (type) {
case MAP:
sql += "parent_map_definition_id = " + id;
break;
case LIST:
sql += "parent_list_definition_id = " + id;
break;
default:
return Collections.emptyList();
}
List<Object[]> results = databaseType.executeSelectSql(connection, sql);
List<PropertyDefinition> ret = new ArrayList<PropertyDefinition>();
for (Object[] row : results) {
PropertyDefinition pd = new PropertyDefinition();
pd.id = ((Number) row[0]).intValue();
pd.name = (String) row[1];
pd.type = PropertyType.fromName((String) row[2]);
pd.simpleType = row[3] != null ? PropertySimpleType.fromName((String) row[3]) : null;
ret.add(pd);
}
return ret;
}
@Override
public String toString() {
return "[id: " + id + ", name: '" + name + "', type: " + type.name() + "]";
}
}
class Property {
int id;
String name;
PropertyType type;
String value;
List<Property> getChildren() throws SQLException {
if (type == PropertyType.SIMPLE || type == PropertyType.OBFUSCATED) {
return Collections.emptyList();
}
String sql = "SELECT id, name, dtype, string_value FROM rhq_config_property WHERE ";
switch (type) {
case MAP:
sql += "parent_map_id = " + id;
break;
case LIST:
sql += "parent_list_id = " + id;
break;
default:
return Collections.emptyList();
}
List<Object[]> results = databaseType.executeSelectSql(connection, sql);
List<Property> ret = new ArrayList<Property>();
for (Object[] row : results) {
Property p = new Property();
p.id = ((Number) row[0]).intValue();
p.name = (String) row[1];
p.type = PropertyType.fromName((String) row[2]);
p.value = (String) row[3];
ret.add(p);
}
return ret;
}
@Override
public String toString() {
return "[id: " + id + ", name: '" + name + "', type: " + type.name() + "]";
}
}
/**
* Based on the entity type that the subclass handles, this method will return the pairs
* of configuration id and the corresponding configuration definition id for each of the
* entities that the subclass handles.
*
* For example if the subclass handles plugin configuration of the resources, the resulting
* map will contain the plugin configuration id of each resource as a key and the value
* of each key will be the plugin configuration definition id of the resource type of the
* resource.
*
* @throws SQLException
*/
protected abstract Map<Integer, Integer> getConfigurationIdConfigurationDefinitionIdPairs() throws SQLException;
/**
* @return a textual human-friendly description of the entity type this subclass handles.
* This is only used in the error reporting.
*/
protected abstract String getEntityTypeDescription();
@Override
public void execute(DatabaseType type, Connection connection) throws SQLException {
this.connection = connection;
this.databaseType = type;
for (Map.Entry<Integer, Integer> confAndDef : getConfigurationIdConfigurationDefinitionIdPairs().entrySet()) {
List<PropertyDefinition> defs = getTopDefinitions(confAndDef.getValue());
List<Property> props = getTopProperties(confAndDef.getKey());
Map<PropertyDefinition, Property> pairs = matchDefinitionsAndProperties(defs, props);
for (Map.Entry<PropertyDefinition, Property> entry : pairs.entrySet()) {
try {
processProperty(entry.getKey(), entry.getValue());
} catch (Exception e) {
throw new SQLException("Failed to obfuscate passwords while processing entities of type ["
+ getEntityTypeDescription()
+ "]. The failure happened while processing: configuration definition id: "
+ confAndDef.getValue() + ", configuration id: " + confAndDef.getKey()
+ ", property definition: " + entry.getKey() + ", property: " + entry.getValue(), e);
}
}
}
}
private void processProperty(PropertyDefinition pd, Property p) throws Exception {
switch (pd.type) {
case LIST:
case MAP:
List<PropertyDefinition> childDefs = pd.getChildDefinitions();
List<Property> childProps = p.getChildren();
Map<PropertyDefinition, Property> pairs = matchDefinitionsAndProperties(childDefs, childProps);
for (Map.Entry<PropertyDefinition, Property> entry : pairs.entrySet()) {
processProperty(entry.getKey(), entry.getValue());
}
break;
case SIMPLE:
if (pd.simpleType == PropertySimpleType.PASSWORD && p.type == PropertyType.SIMPLE) {
String sql;
if (p.value != null) {
String obfuscatedValue = Obfuscator.encode(p.value);
sql =
"UPDATE rhq_config_property SET string_value = '" + obfuscatedValue
+ "', dtype = 'obfuscated' WHERE id = " + p.id;
} else {
sql = "UPDATE rhq_config_property SET dtype='obfuscated' WHERE id = " + p.id;
}
databaseType.executeSql(connection, sql);
}
break;
}
}
private List<PropertyDefinition> getTopDefinitions(int configurationDefinitionId) throws SQLException {
String sql =
"SELECT id, name, dtype, simple_type FROM rhq_config_prop_def WHERE config_def_id = " + configurationDefinitionId;
List<Object[]> results = databaseType.executeSelectSql(connection, sql);
List<PropertyDefinition> ret = new ArrayList<PropertyDefinition>();
for (Object[] row : results) {
PropertyDefinition pd = new PropertyDefinition();
pd.id = ((Number) row[0]).intValue();
pd.name = (String) row[1];
pd.type = PropertyType.fromName((String) row[2]);
pd.simpleType = row[3] != null ? PropertySimpleType.fromName((String) row[3]) : null;
ret.add(pd);
}
return ret;
}
private List<Property> getTopProperties(int configurationId) throws SQLException {
String sql =
"SELECT id, name, dtype, string_value FROM rhq_config_property WHERE configuration_id = " + configurationId;
List<Object[]> results = databaseType.executeSelectSql(connection, sql);
List<Property> ret = new ArrayList<Property>();
for (Object[] row : results) {
Property p = new Property();
p.id = ((Number) row[0]).intValue();
p.name = (String) row[1];
p.type = PropertyType.fromName((String) row[2]);
p.value = (String) row[3];
ret.add(p);
}
return ret;
}
private Map<PropertyDefinition, Property> matchDefinitionsAndProperties(List<PropertyDefinition> defs,
List<Property> props) {
class DefAndProp {
PropertyDefinition def;
Property prop;
}
Map<String, DefAndProp> mapping = new HashMap<String, DefAndProp>();
for (PropertyDefinition pd : defs) {
DefAndProp dap = new DefAndProp();
dap.def = pd;
mapping.put(pd.name, dap);
}
for (Property p : props) {
DefAndProp dap = mapping.get(p.name);
if (dap != null) {
dap.prop = p;
}
}
Map<PropertyDefinition, Property> ret = new HashMap<PropertyDefinition, Property>();
for (DefAndProp dap : mapping.values()) {
ret.put(dap.def, dap.prop);
}
return ret;
}
}