diff --git a/base/src/org/compiere/process/ColumnEncryption.java b/base/src/org/compiere/process/ColumnEncryption.java index f35a824e8d..e75b6062ea 100644 --- a/base/src/org/compiere/process/ColumnEncryption.java +++ b/base/src/org/compiere/process/ColumnEncryption.java @@ -1,56 +1,82 @@ -/****************************************************************************** - * Product: Adempiere ERP & CRM Smart Business Solution * - * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. * - * This program is free software; you can redistribute it and/or modify it * - * under the terms version 2 of the GNU General Public License as published * - * by the Free Software Foundation. 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., * - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * - * For the text or an alternative of this public license, you may reach us * - * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA * - * or via info@compiere.org or http://www.compiere.org/license.html * - *****************************************************************************/ +/********************************************************************** + * This file is part of Adempiere ERP Bazaar * + * http://www.adempiere.org * + * * + * Copyright (C) 1999 - 2006 Compiere Inc. * + * Copyright (C) Contributors * + * * + * 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; either version 2 * + * of the License, or (at your option) any later version. * + * * + * 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., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - Bahman Movaqar (bmovaqar AT users.sf.net) * + **********************************************************************/ package org.compiere.process; +import java.sql.Connection; +import java.sql.PreparedStatement; import java.util.logging.*; + +import java.sql.ResultSet; + import org.compiere.model.*; import org.compiere.util.*; - /** - * Column Encryption Test - * - * @author Jorg Janke - * @version $Id: ColumnEncryption.java,v 1.2 2006/07/30 00:51:01 jjanke Exp $ + * Column Encryption Test + * + * @author Jorg Janke + * @version $Id: ColumnEncryption.java,v 1.2 2006/07/30 00:51:01 jjanke Exp $ */ -public class ColumnEncryption extends SvrProcess -{ - /** Enable/Disable Encryption */ - private boolean p_IsEncrypted = false; - /** Change Encryption Settings */ - private boolean p_ChangeSetting = false; - /** Maximum Length */ - private int p_MaxLength = 0; - /** Test Value */ - private String p_TestValue = null; - /** The Column */ - private int p_AD_Column_ID = 0; +public class ColumnEncryption extends SvrProcess { + /** Enable/Disable Encryption */ + private boolean p_IsEncrypted = false; + + /** Change Encryption Settings */ + private boolean p_ChangeSetting = false; + + /** Maximum Length */ + private int p_MaxLength = 0; + + /** Test Value */ + private String p_TestValue = null; + + /** The Column */ + private int p_AD_Column_ID = 0; + + /** + * All the resizing and encrypting database are managed by this + * transaction. + */ + private Trx m_trx; /** - * Prepare - e.g., get Parameters. + * All the resizing and encrypting database work goes through this + * connection. */ - protected void prepare() - { + private Connection m_conn; + + /** + * Prepare - e.g., get Parameters. + */ + protected void prepare() { ProcessInfoParameter[] para = getParameter(); - for (int i = 0; i < para.length; i++) - { + for (int i = 0; i < para.length; i++) { String name = para[i].getParameterName(); if (para[i].getParameter() == null) - ; + ; else if (name.equals("IsEncrypted")) p_IsEncrypted = "Y".equals(para[i].getParameter()); else if (name.equals("ChangeSetting")) @@ -58,87 +84,80 @@ public class ColumnEncryption extends SvrProcess else if (name.equals("MaxLength")) p_MaxLength = para[i].getParameterAsInt(); else if (name.equals("TestValue")) - p_TestValue = (String)para[i].getParameter(); + p_TestValue = (String) para[i].getParameter(); else log.log(Level.SEVERE, "Unknown Parameter: " + name); } p_AD_Column_ID = getRecord_ID(); - } // prepare + } // prepare /** - * Process - * @return info - * @throws Exception + * Process + * + * @return info + * @throws Exception */ - protected String doIt () throws Exception - { - log.info("AD_Column_ID=" + p_AD_Column_ID - + ", IsEncrypted=" + p_IsEncrypted - + ", ChangeSetting=" + p_ChangeSetting - + ", MaxLength=" + p_MaxLength); - MColumn column = new MColumn (getCtx(), p_AD_Column_ID, null); + protected String doIt() throws Exception { + log.info("AD_Column_ID=" + p_AD_Column_ID + ", IsEncrypted=" + + p_IsEncrypted + ", ChangeSetting=" + p_ChangeSetting + + ", MaxLength=" + p_MaxLength); + MColumn column = new MColumn(getCtx(), p_AD_Column_ID, get_TrxName()); if (column.get_ID() == 0 || column.get_ID() != p_AD_Column_ID) - throw new AdempiereUserError("@NotFound@ @AD_Column_ID@ - " + p_AD_Column_ID); + throw new AdempiereUserError("@NotFound@ @AD_Column_ID@ - " + + p_AD_Column_ID); // String columnName = column.getColumnName(); int dt = column.getAD_Reference_ID(); - - // Can it be enabled? - if (column.isKey() - || column.isParent() - || column.isStandardColumn() - || column.isVirtualColumn() - || column.isIdentifier() - || column.isTranslated() - || DisplayType.isLookup(dt) - || DisplayType.isLOB(dt) - || "DocumentNo".equalsIgnoreCase(column.getColumnName()) - || "Value".equalsIgnoreCase(column.getColumnName()) - || "Name".equalsIgnoreCase(column.getColumnName())) - { - if (column.isEncrypted()) - { + + // Can it be enabled? + if (column.isKey() || column.isParent() || column.isStandardColumn() + || column.isVirtualColumn() || column.isIdentifier() + || column.isTranslated() || DisplayType.isLookup(dt) + || DisplayType.isLOB(dt) + || "DocumentNo".equalsIgnoreCase(column.getColumnName()) + || "Value".equalsIgnoreCase(column.getColumnName()) + || "Name".equalsIgnoreCase(column.getColumnName())) { + if (column.isEncrypted()) { column.setIsEncrypted(false); column.save(); } return columnName + ": cannot be encrypted"; } - - // Start - addLog(0, null, null, "Encryption Class = " + SecureEngine.getClassName()); + + // Start + addLog(0, null, null, "Encryption Class = " + + SecureEngine.getClassName()); boolean error = false; - - // Test Value - if (p_TestValue != null && p_TestValue.length() > 0) - { + + // Test Value + if (p_TestValue != null && p_TestValue.length() > 0) { String encString = SecureEngine.encrypt(p_TestValue); addLog(0, null, null, "Encrypted Test Value=" + encString); String clearString = SecureEngine.decrypt(encString); if (p_TestValue.equals(clearString)) - addLog(0, null, null, "Decrypted=" + clearString - + " (same as test value)"); - else - { - addLog(0, null, null, "Decrypted=" + clearString - + " (NOT the same as test value - check algorithm)"); + addLog(0, null, null, "Decrypted=" + clearString + + " (same as test value)"); + else { + addLog(0, null, null, "Decrypted=" + clearString + + " (NOT the same as test value - check algorithm)"); error = true; } int encLength = encString.length(); - addLog(0, null, null, "Test Length=" + p_TestValue.length() + " -> " + encLength); + addLog(0, null, null, "Test Length=" + p_TestValue.length() + + " -> " + encLength); if (encLength <= column.getFieldLength()) - addLog(0, null, null, "Encrypted Length (" + encLength - + ") fits into field (" + column.getFieldLength() + ")"); - else - { - addLog(0, null, null, "Encrypted Length (" + encLength - + ") does NOT fit into field (" + column.getFieldLength() + ") - resize field"); + addLog(0, null, null, "Encrypted Length (" + encLength + + ") fits into field (" + column.getFieldLength() + ")"); + else { + addLog(0, null, null, "Encrypted Length (" + encLength + + ") does NOT fit into field (" + + column.getFieldLength() + ") - resize field"); error = true; } } - // Length Test - if (p_MaxLength != 0) - { + // Length Test + if (p_MaxLength != 0) { String testClear = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; while (testClear.length() < p_MaxLength) testClear += testClear; @@ -147,32 +166,234 @@ public class ColumnEncryption extends SvrProcess // String encString = SecureEngine.encrypt(testClear); int encLength = encString.length(); - addLog(0, null, null, "Test Max Length=" + testClear.length() + " -> " + encLength); + addLog(0, null, null, "Test Max Length=" + testClear.length() + + " -> " + encLength); if (encLength <= column.getFieldLength()) - addLog(0, null, null, "Encrypted Max Length (" + encLength - + ") fits into field (" + column.getFieldLength() + ")"); - else - { - addLog(0, null, null, "Encrypted Max Length (" + encLength - + ") does NOT fit into field (" + column.getFieldLength() + ") - resize field"); + addLog(0, null, null, "Encrypted Max Length (" + encLength + + ") fits into field (" + column.getFieldLength() + ")"); + else { + addLog(0, null, null, "Encrypted Max Length (" + encLength + + ") does NOT fit into field (" + + column.getFieldLength() + ") - resize field"); error = true; } } - if (p_IsEncrypted != column.isEncrypted()) - { - if (error || !p_ChangeSetting) - addLog(0, null, null, "Encryption NOT changed - Encryption=" + column.isEncrypted()); - else - { - column.setIsEncrypted(p_IsEncrypted); - if (column.save()) - addLog(0, null, null, "Encryption CHANGED - Encryption=" + column.isEncrypted()); - else - addLog(0, null, null, "Save Error"); + // If only user chooses both encrypt the contents and override current + // settings resize the physical column and encrypt all its contents. + if (p_IsEncrypted && p_ChangeSetting) { + // If the column has already been encrypted, show a warning message + // and exit. + if (column.isEncrypted()) { + log.severe("EncryptError: Column already encrypted."); + throw new Exception(); + } + // Init the transaction and setup the connection. + m_trx = Trx.get(get_TrxName(), true); + if ((m_conn = m_trx.getConnection()) == null) { + log.warning("EncryptError: No connections available"); + throw new Exception(); + } + m_conn.setAutoCommit(false); + + int columnID = column.get_ID(); + MTable table = MTable.get(getCtx(), column.getAD_Table_ID()); + String tableName = table.getTableName(); + + // Check if the encryption exceeds the current length. + int oldLength = column.getFieldLength(); + int newLength = encryptedColumnLength(oldLength); + if (newLength > oldLength) + if (changeFieldLength(columnID, columnName, newLength, + tableName) == -1) { + log.warning("EncryptError [ChangeFieldLength]: " + + "ColumnID=" + columnID + ", NewLength=" + + newLength); + throw new Exception(); + } + + // Encrypt column contents. + if (encryptColumnContents(columnName, column.getAD_Table_ID()) == -1) { + log.warning("EncryptError: No records encrypted."); + throw new Exception(); + } + + if (p_IsEncrypted != column.isEncrypted()) { + if (error || !p_ChangeSetting) + addLog(0, null, null, "Encryption NOT changed - Encryption=" + + column.isEncrypted()); + else { + column.setIsEncrypted(p_IsEncrypted); + if (column.save()) + addLog(0, null, null, "Encryption CHANGED - Encryption=" + + column.isEncrypted()); + else + addLog(0, null, null, "Save Error"); + } } } + return "Encryption=" + column.isEncrypted(); - } // doIt + } // doIt -} // EncryptionTest + /** + * Encrypt all the contents of a database column. + * + * @param columnName + * The ID of the column to be encrypted. + * @param tableID + * The ID of the table which owns the column. + * @return The number of rows effected or -1 in case of errors. + * @throws Exception + */ + private int encryptColumnContents(String columnName, int tableID) + throws Exception { + // Find the table name + String tableName = MTable.getTableName(getCtx(), tableID); + + return encryptColumnContents(columnName, tableName); + } // encryptColumnContents + + /** + * Encrypt all the contents of a database column. + * + * @param columnName + * The ID of the column to be encrypted. + * @param tableName + * The name of the table which owns the column. + * @return The number of rows effected or -1 in case of errors. + */ + private int encryptColumnContents(String columnName, String tableName) + throws Exception { + int recordsEncrypted = 0; + String idColumnName = tableName + "_ID"; + + StringBuffer selectSql = new StringBuffer(); + selectSql.append("SELECT " + idColumnName + "," + columnName); + selectSql.append(" FROM " + tableName); + selectSql.append(" ORDER BY " + idColumnName); + + StringBuffer updateSql = new StringBuffer(); + updateSql.append("UPDATE " + tableName); + updateSql.append(" SET " + columnName + "=?"); + updateSql.append(" WHERE " + idColumnName + "=?"); + + PreparedStatement selectStmt = null; + PreparedStatement updateStmt = null; + + selectStmt = m_conn.prepareStatement(selectSql.toString(), + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + updateStmt = m_conn.prepareStatement(updateSql.toString()); + + ResultSet rs = selectStmt.executeQuery(); + + for (recordsEncrypted = 0; rs.next(); ++recordsEncrypted) { + // Get the row id and column value + int id = rs.getInt(1); + String value = rs.getString(2); + // Encrypt the value + value = SecureEngine.encrypt(value); + // Update the row + updateStmt.setString(1, value); + updateStmt.setInt(2, id); + if (updateStmt.executeUpdate() != 1) { + log.warning("EncryptError: Table=" + tableName + ", ID=" + id); + throw new Exception(); + } + } + + rs.close(); + selectStmt.close(); + updateStmt.close(); + + return recordsEncrypted; + } // encryptColumnContents + + /** + * Determines the length of the encrypted column. + * + * @param currentColSize + * Current column size + * @return The length of the encrypted column. + */ + private int encryptedColumnLength(int colLength) { + String str = ""; + + for (int i = 0; i < colLength; i++) { + str += "1"; + } + str = SecureEngine.encrypt(str); + + return str.length(); + } // encryptedColumnLength + + /** + * Change the column length. + * + * @param columnID + * ID of the column + * @param tableName + * The name of the table which owns the column + * @param length + * New length of the column + * @return The number of rows effected, 1 upon success and -1 for failure. + */ + private int changeFieldLength(int columnID, String columnName, int length, + String tableName) throws Exception { + int rowsEffected = -1; + + // Select SQL + StringBuffer selectSql = new StringBuffer(); + selectSql.append("SELECT FieldLength"); + selectSql.append(" FROM AD_Column"); + selectSql.append(" WHERE AD_Column_ID=?"); + + // Alter SQL + StringBuffer alterSql = new StringBuffer(); + alterSql.append("ALTER TABLE " + tableName); + alterSql.append(" MODIFY " + columnName); + alterSql.append(" NVARCHAR2("); + alterSql.append(length + ") "); + + // Update SQL + StringBuffer updateSql = new StringBuffer(); + updateSql.append("UPDATE AD_Column"); + updateSql.append(" SET FieldLength=" + length); + updateSql.append(" WHERE AD_Column_ID=" + columnID); + + PreparedStatement selectStmt = null; + + selectStmt = m_conn.prepareStatement(selectSql.toString(), + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + + selectStmt.setInt(1, columnID); + ResultSet rs = selectStmt.executeQuery(); + + if (rs.next()) { + // Change the column size physically. + if (DB.executeUpdate(alterSql.toString(), false, m_trx + .getTrxName()) == -1) { + log.warning("EncryptError [ChangeFieldLength]: ColumnID=" + + columnID + ", NewLength=" + length); + throw new Exception(); + } + + // Change the column size in AD. + if (DB.executeUpdate(updateSql.toString(), false, m_trx + .getTrxName()) == -1) { + log.warning("EncryptError [ChangeFieldLength]: ColumnID=" + + columnID + ", NewLength=" + length); + throw new Exception(); + } + } + + rs.close(); + selectStmt.close(); + + // Update number of rows effected. + rowsEffected++; + + return rowsEffected; + } // changeFieldLength + +} // EncryptionTest \ No newline at end of file