IDEMPIERE-347 passwords hash

Thanks to Adaxa Australia -> http://adempiere.hg.sourceforge.net/hgweb/adempiere/contribution_adaxa/rev/6d9090d8a9f6
Ken Longnan for iDempiere integration work at -> https://bitbucket.org/longnan/idempiere-longnan
Juliana Corredor for further integration and tests on iDempiere and making the process safer and backward compatible via sysconfig
Carlos Ruiz (globalqss) peer reviewed, tested and finished some extra required
Sponsored by Trek Global -> http://www.trekglobal.com/
This commit is contained in:
Carlos Ruiz 2012-07-31 18:46:45 -05:00
parent f1f7168861
commit 26a07ca3c8
16 changed files with 758 additions and 144 deletions

View File

@ -0,0 +1,78 @@
-- 29/06/2011 1:33:27 PM
-- -
UPDATE AD_Column SET FieldLength=1024,Updated=TO_DATE('2011-06-29 13:33:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=417
;
-- 29/06/2011 1:33:31 PM
-- -
ALTER TABLE AD_User MODIFY Password NVARCHAR2(1024) DEFAULT NULL
;
-- 29/06/2011 1:34:52 PM
-- -
INSERT INTO AD_Element (AD_Client_ID,AD_Element_ID,AD_Org_ID,ColumnName,Created,CreatedBy,Description,EntityType,IsActive,Name,PrintName,Updated,UpdatedBy) VALUES (0,55218,0,'Salt',TO_DATE('2011-06-29 13:34:50','YYYY-MM-DD HH24:MI:SS'),100,'Random data added to improve password hash effectiveness','D','Y','Salt','Salt',TO_DATE('2011-06-29 13:34:50','YYYY-MM-DD HH24:MI:SS'),100)
;
-- 29/06/2011 1:34:52 PM
-- -
INSERT INTO AD_Element_Trl (AD_Language,AD_Element_ID, Description,Help,Name,PO_Description,PO_Help,PO_Name,PO_PrintName,PrintName, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Element_ID, t.Description,t.Help,t.Name,t.PO_Description,t.PO_Help,t.PO_Name,t.PO_PrintName,t.PrintName, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Element t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Element_ID=55218 AND NOT EXISTS (SELECT * FROM AD_Element_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Element_ID=t.AD_Element_ID)
;
-- 29/06/2011 1:36:39 PM
-- -
ALTER TABLE AD_User ADD Salt NVARCHAR2(16) DEFAULT NULL
;
-- 29/06/2011 1:37:48 PM
-- -
INSERT INTO AD_Column (AD_Client_ID,AD_Column_ID,AD_Element_ID,AD_Org_ID,AD_Reference_ID,AD_Table_ID,ColumnName,Created,CreatedBy,Description,EntityType,FieldLength,IsActive,IsAllowLogging,IsAlwaysUpdateable,IsAutocomplete,IsEncrypted,IsIdentifier,IsKey,IsMandatory,IsParent,IsSelectionColumn,IsSyncDatabase,IsTranslated,IsUpdateable,Name,SeqNo,Updated,UpdatedBy,Version) VALUES (0,61756,55218,0,10,114,'Salt',TO_DATE('2011-06-29 13:37:47','YYYY-MM-DD HH24:MI:SS'),100,'Random data added to improve password hash effectiveness','D',16,'Y','Y','N','N','N','N','N','N','N','N','N','N','N','Salt',0,TO_DATE('2011-06-29 13:37:47','YYYY-MM-DD HH24:MI:SS'),100,0)
;
-- 29/06/2011 1:37:48 PM
-- -
INSERT INTO AD_Column_Trl (AD_Language,AD_Column_ID, Name, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Column_ID, t.Name, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Column t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Column_ID=61756 AND NOT EXISTS (SELECT * FROM AD_Column_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Column_ID=t.AD_Column_ID)
;
-- 29/06/2011 4:59:43 PM
-- -
INSERT INTO AD_Process (AccessLevel,AD_Client_ID,AD_Org_ID,AD_Process_ID,Classname,CopyFromProcess,Created,CreatedBy,Description,EntityType,Help,IsActive,IsBetaFunctionality,IsDirectPrint,IsReport,IsServerProcess,Name,ShowHelp,Statistic_Count,Statistic_Seconds,Updated,UpdatedBy,Value) VALUES ('4',0,0,53259,'org.compiere.process.HashPasswords','N',TO_DATE('2011-06-29 16:59:41','YYYY-MM-DD HH24:MI:SS'),100,'Convert existing plain text/encrypted user passwords to one way hash','D','This process will overwrite existing user passwords with a salted SHA-512 hash of the password so that they cannot be recovered if your database is compromised.
(Note: If your password column is currently encrypted, the hash will also be encrypted.)','Y','N','N','N','N','Convert passwords to hashes','Y',0,0,TO_DATE('2011-06-29 16:59:41','YYYY-MM-DD HH24:MI:SS'),100,'AD_User_HashPassword')
;
-- 29/06/2011 4:59:43 PM
-- -
INSERT INTO AD_Process_Trl (AD_Language,AD_Process_ID, Description,Help,Name, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Process_ID, t.Description,t.Help,t.Name, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Process t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Process_ID=53259 AND NOT EXISTS (SELECT * FROM AD_Process_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Process_ID=t.AD_Process_ID)
;
-- 29/06/2011 5:00:28 PM
-- -
INSERT INTO AD_Menu (Action,AD_Client_ID,AD_Menu_ID,AD_Org_ID,AD_Process_ID,Created,CreatedBy,EntityType,IsActive,IsCentrallyMaintained,IsReadOnly,IsSOTrx,IsSummary,Name,Updated,UpdatedBy) VALUES ('P',0,53348,0,53259,TO_DATE('2011-06-29 17:00:27','YYYY-MM-DD HH24:MI:SS'),100,'D','Y','Y','N','N','N','Hash Passwords',TO_DATE('2011-06-29 17:00:27','YYYY-MM-DD HH24:MI:SS'),100)
;
-- 29/06/2011 5:00:28 PM
-- -
INSERT INTO AD_Menu_Trl (AD_Language,AD_Menu_ID, Description,Name, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Menu_ID, t.Description,t.Name, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Menu t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Menu_ID=53348 AND NOT EXISTS (SELECT * FROM AD_Menu_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Menu_ID=t.AD_Menu_ID)
;
-- 29/06/2011 5:00:28 PM
-- -
INSERT INTO AD_TreeNodeMM (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo) SELECT t.AD_Client_ID, 0, 'Y', SysDate, 100, SysDate, 100,t.AD_Tree_ID, 53348, 0, 999 FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='MM' AND NOT EXISTS (SELECT * FROM AD_TreeNodeMM e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=53348)
;
-- 29/06/2011 5:00:53 PM
-- -
UPDATE AD_TreeNodeMM SET Parent_ID=367, SeqNo=999, Updated=SysDate WHERE AD_Tree_ID=10 AND Node_ID=53348
;
-- Jul 24, 2012 5:50:06 PM COT
INSERT INTO AD_SysConfig (AD_SysConfig_ID,EntityType,ConfigurationLevel,Value,Description,AD_SysConfig_UU,Created,Updated,AD_Client_ID,AD_Org_ID,CreatedBy,IsActive,UpdatedBy,Name) VALUES (200013,'D','S','N','Enable hash passwords, please use Hash Password process to enable','569577c3-3bfa-4d0f-a2e3-d98752164667',TO_DATE('2012-07-24 17:50:04','YYYY-MM-DD HH24:MI:SS'),TO_DATE('2012-07-24 17:50:04','YYYY-MM-DD HH24:MI:SS'),0,0,0,'Y',0,'USER_PASSWORD_HASH')
;
UPDATE AD_System
SET LastMigrationScriptApplied='854_PasswordHash_IDEMPIERE-347.sql'
WHERE LastMigrationScriptApplied<'854_PasswordHash_IDEMPIERE-347.sql'
OR LastMigrationScriptApplied IS NULL
;

View File

@ -0,0 +1,78 @@
-- 29/06/2011 1:33:27 PM
-- -
UPDATE AD_Column SET FieldLength=1024,Updated=TO_TIMESTAMP('2011-06-29 13:33:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=417
;
-- 29/06/2011 1:33:31 PM
-- -
INSERT INTO t_alter_column values('ad_user','Password','VARCHAR(1024)',null,'NULL')
;
-- 29/06/2011 1:34:52 PM
-- -
INSERT INTO AD_Element (AD_Client_ID,AD_Element_ID,AD_Org_ID,ColumnName,Created,CreatedBy,Description,EntityType,IsActive,Name,PrintName,Updated,UpdatedBy) VALUES (0,55218,0,'Salt',TO_TIMESTAMP('2011-06-29 13:34:50','YYYY-MM-DD HH24:MI:SS'),100,'Random data added to improve password hash effectiveness','D','Y','Salt','Salt',TO_TIMESTAMP('2011-06-29 13:34:50','YYYY-MM-DD HH24:MI:SS'),100)
;
-- 29/06/2011 1:34:52 PM
-- -
INSERT INTO AD_Element_Trl (AD_Language,AD_Element_ID, Description,Help,Name,PO_Description,PO_Help,PO_Name,PO_PrintName,PrintName, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Element_ID, t.Description,t.Help,t.Name,t.PO_Description,t.PO_Help,t.PO_Name,t.PO_PrintName,t.PrintName, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Element t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Element_ID=55218 AND NOT EXISTS (SELECT * FROM AD_Element_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Element_ID=t.AD_Element_ID)
;
-- 29/06/2011 1:36:39 PM
-- -
ALTER TABLE AD_User ADD COLUMN Salt VARCHAR(16) DEFAULT NULL
;
-- 29/06/2011 1:37:48 PM
-- -
INSERT INTO AD_Column (AD_Client_ID,AD_Column_ID,AD_Element_ID,AD_Org_ID,AD_Reference_ID,AD_Table_ID,ColumnName,Created,CreatedBy,Description,EntityType,FieldLength,IsActive,IsAllowLogging,IsAlwaysUpdateable,IsAutocomplete,IsEncrypted,IsIdentifier,IsKey,IsMandatory,IsParent,IsSelectionColumn,IsSyncDatabase,IsTranslated,IsUpdateable,Name,SeqNo,Updated,UpdatedBy,Version) VALUES (0,61756,55218,0,10,114,'Salt',TO_TIMESTAMP('2011-06-29 13:37:47','YYYY-MM-DD HH24:MI:SS'),100,'Random data added to improve password hash effectiveness','D',16,'Y','Y','N','N','N','N','N','N','N','N','N','N','N','Salt',0,TO_TIMESTAMP('2011-06-29 13:37:47','YYYY-MM-DD HH24:MI:SS'),100,0)
;
-- 29/06/2011 1:37:48 PM
-- -
INSERT INTO AD_Column_Trl (AD_Language,AD_Column_ID, Name, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Column_ID, t.Name, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Column t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Column_ID=61756 AND NOT EXISTS (SELECT * FROM AD_Column_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Column_ID=t.AD_Column_ID)
;
-- 29/06/2011 4:59:43 PM
-- -
INSERT INTO AD_Process (AccessLevel,AD_Client_ID,AD_Org_ID,AD_Process_ID,Classname,CopyFromProcess,Created,CreatedBy,Description,EntityType,Help,IsActive,IsBetaFunctionality,IsDirectPrint,IsReport,IsServerProcess,Name,ShowHelp,Statistic_Count,Statistic_Seconds,Updated,UpdatedBy,Value) VALUES ('4',0,0,53259,'org.compiere.process.HashPasswords','N',TO_TIMESTAMP('2011-06-29 16:59:41','YYYY-MM-DD HH24:MI:SS'),100,'Convert existing plain text/encrypted user passwords to one way hash','D','This process will overwrite existing user passwords with a salted SHA-512 hash of the password so that they cannot be recovered if your database is compromised.
(Note: If your password column is currently encrypted, the hash will also be encrypted.)','Y','N','N','N','N','Convert passwords to hashes','Y',0,0,TO_TIMESTAMP('2011-06-29 16:59:41','YYYY-MM-DD HH24:MI:SS'),100,'AD_User_HashPassword')
;
-- 29/06/2011 4:59:43 PM
-- -
INSERT INTO AD_Process_Trl (AD_Language,AD_Process_ID, Description,Help,Name, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Process_ID, t.Description,t.Help,t.Name, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Process t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Process_ID=53259 AND NOT EXISTS (SELECT * FROM AD_Process_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Process_ID=t.AD_Process_ID)
;
-- 29/06/2011 5:00:28 PM
-- -
INSERT INTO AD_Menu (Action,AD_Client_ID,AD_Menu_ID,AD_Org_ID,AD_Process_ID,Created,CreatedBy,EntityType,IsActive,IsCentrallyMaintained,IsReadOnly,IsSOTrx,IsSummary,Name,Updated,UpdatedBy) VALUES ('P',0,53348,0,53259,TO_TIMESTAMP('2011-06-29 17:00:27','YYYY-MM-DD HH24:MI:SS'),100,'D','Y','Y','N','N','N','Hash Passwords',TO_TIMESTAMP('2011-06-29 17:00:27','YYYY-MM-DD HH24:MI:SS'),100)
;
-- 29/06/2011 5:00:28 PM
-- -
INSERT INTO AD_Menu_Trl (AD_Language,AD_Menu_ID, Description,Name, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Menu_ID, t.Description,t.Name, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Menu t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Menu_ID=53348 AND NOT EXISTS (SELECT * FROM AD_Menu_Trl tt WHERE tt.AD_Language=l.AD_Language AND tt.AD_Menu_ID=t.AD_Menu_ID)
;
-- 29/06/2011 5:00:28 PM
-- -
INSERT INTO AD_TreeNodeMM (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo) SELECT t.AD_Client_ID, 0, 'Y', CURRENT_TIMESTAMP, 100, CURRENT_TIMESTAMP, 100,t.AD_Tree_ID, 53348, 0, 999 FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='MM' AND NOT EXISTS (SELECT * FROM AD_TreeNodeMM e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=53348)
;
-- 29/06/2011 5:00:53 PM
-- -
UPDATE AD_TreeNodeMM SET Parent_ID=367, SeqNo=999, Updated=CURRENT_TIMESTAMP WHERE AD_Tree_ID=10 AND Node_ID=53348
;
-- Jul 24, 2012 5:50:06 PM COT
INSERT INTO AD_SysConfig (AD_SysConfig_ID,EntityType,ConfigurationLevel,Value,Description,AD_SysConfig_UU,Created,Updated,AD_Client_ID,AD_Org_ID,CreatedBy,IsActive,UpdatedBy,Name) VALUES (200013,'D','S','N','Enable hash passwords, please use Hash Password process to enable','569577c3-3bfa-4d0f-a2e3-d98752164667',TO_TIMESTAMP('2012-07-24 17:50:04','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2012-07-24 17:50:04','YYYY-MM-DD HH24:MI:SS'),0,0,0,'Y',0,'USER_PASSWORD_HASH')
;
UPDATE AD_System
SET LastMigrationScriptApplied='854_PasswordHash_IDEMPIERE-347.sql'
WHERE LastMigrationScriptApplied<'854_PasswordHash_IDEMPIERE-347.sql'
OR LastMigrationScriptApplied IS NULL
;

View File

@ -0,0 +1,78 @@
/******************************************************************************
* 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 *
*****************************************************************************/
package org.compiere.process;
import java.util.List;
import org.adempiere.exceptions.AdempiereException;
import org.compiere.model.MSysConfig;
import org.compiere.model.MTable;
import org.compiere.model.MUser;
import org.compiere.model.SystemIDs;
/**
* Hash existing passwords
*
*/
public class HashPasswords extends SvrProcess
{
/**
* Prepare - e.g., get Parameters.
*/
protected void prepare()
{
} // prepare
/**
* Perform process.
* @return Message
* @throws Exception if not successful
*/
protected String doIt() throws Exception
{
boolean hash_password = MSysConfig.getBooleanValue("USER_PASSWORD_HASH", false);
if (hash_password)
throw new AdempiereException("Passwords already hashed");
String where = " Password IS NOT NULL AND Salt IS NULL ";
// update the sysconfig key to Y out of trx and reset the cache
MSysConfig conf = new MSysConfig(getCtx(), SystemIDs.SYSCONFIG_USER_HASH_PASSWORD, null);
conf.setValue("Y");
conf.saveEx();
MSysConfig.resetCache();
int count = 0;
try {
List<MUser> users = MTable.get(getCtx(), MUser.Table_ID).createQuery( where, get_TrxName()).list();
for ( MUser user : users )
{
user.setPassword(user.getPassword());
count++;
user.saveEx();
}
} catch (Exception e) {
// reset to N on exception
conf.setValue("N");
conf.saveEx();
throw e;
}
return "@Updated@ " + count;
} // doIt
} // HashPasswords

View File

@ -18,8 +18,8 @@ package org.compiere.process;
import java.util.logging.Level;
import org.compiere.model.MSysConfig;
import org.compiere.model.MUser;
import org.compiere.util.DB;
import org.compiere.util.Util;
/**
@ -78,7 +78,7 @@ public class UserPassword extends SvrProcess
MUser operator = MUser.get(getCtx(), getAD_User_ID());
log.fine("User=" + user + ", Operator=" + operator);
boolean hash_password = MSysConfig.getBooleanValue("USER_PASSWORD_HASH", false);
// Do we need a password ?
if (Util.isEmpty(p_OldPassword)) // Password required
@ -88,31 +88,17 @@ public class UserPassword extends SvrProcess
|| !operator.isAdministrator())
throw new IllegalArgumentException("@OldPasswordMandatory@");
}
// is entered Password correct ?
else if (!p_OldPassword.equals(user.getPassword()))
else {
if (hash_password){
if (!user.authenticateHash(p_OldPassword) )
throw new IllegalArgumentException("@OldPasswordNoMatch@");
} else{
if (!p_OldPassword.equals(user.getPassword()))
throw new IllegalArgumentException("@OldPasswordNoMatch@");
// Change Super User
if (p_AD_User_ID == 0)
{
String sql = "UPDATE AD_User SET Updated=SysDate, UpdatedBy=" + getAD_User_ID();
if (!Util.isEmpty(p_NewPassword))
sql += ", Password=" + DB.TO_STRING(p_NewPassword);
if (!Util.isEmpty(p_NewEMail))
sql += ", Email=" + DB.TO_STRING(p_NewEMail);
if (!Util.isEmpty(p_NewEMailUser))
sql += ", EmailUser=" + DB.TO_STRING(p_NewEMailUser);
if (!Util.isEmpty(p_NewEMailUserPW))
sql += ", EmailUserPW=" + DB.TO_STRING(p_NewEMailUserPW);
sql += " WHERE AD_User_ID=0";
if (DB.executeUpdate(sql, get_TrxName()) == 1)
return "OK";
else
return "@Error@";
}
else
{
}
if (!Util.isEmpty(p_NewPassword))
user.setPassword(p_NewPassword);
if (!Util.isEmpty(p_NewEMail))
@ -121,13 +107,9 @@ public class UserPassword extends SvrProcess
user.setEMailUser(p_NewEMailUser);
if (!Util.isEmpty(p_NewEMailUserPW))
user.setEMailUserPW(p_NewEMailUserPW);
//
if (user.save())
user.saveEx();
return "OK";
else
return "@Error@";
}
} // doIt
} // UserPassword

View File

@ -1443,18 +1443,9 @@ public class GridTable extends AbstractTableModel
&& (Env.getAD_User_ID(m_ctx) == USER_SYSTEM || Env.getAD_User_ID(m_ctx) == USER_SUPERUSER) // user must know what is doing -> just allowed to System or SuperUser (Hardcoded)
&& getKeyID(m_rowChanged) == 0) { // the record being changed has ID = 0
String tablename = getTableName(); // just the allowed tables (HardCoded)
if (tablename.equals("AD_Org") ||
tablename.equals("AD_ReportView") ||
tablename.equals("AD_Role") ||
tablename.equals("AD_System") ||
tablename.equals("AD_User") ||
tablename.equals("C_DocType") ||
tablename.equals("GL_Category") ||
tablename.equals("M_AttributeSet") ||
tablename.equals("M_AttributeSetInstance")) {
if (MTable.isZeroIDTable(tablename))
specialZeroUpdate = true;
}
}
// Check Mandatory
String missingColumns = getMandatory(rowData);

View File

@ -459,6 +459,19 @@ public interface I_AD_User
/** Get Process Now */
public boolean isProcessing();
/** Column name Salt */
public static final String COLUMNNAME_Salt = "Salt";
/** Set Salt.
* Random data added to improve password hash effectiveness
*/
public void setSalt (String Salt);
/** Get Salt.
* Random data added to improve password hash effectiveness
*/
public String getSalt();
/** Column name Supervisor_ID */
public static final String COLUMNNAME_Supervisor_ID = "Supervisor_ID";

View File

@ -50,11 +50,10 @@ import org.compiere.util.DB;
*/
public class MTable extends X_AD_Table
{
/**
*
*/
private static final long serialVersionUID = -2367316254623142732L;
private static final long serialVersionUID = 8264472455498363565L;
public final static int MAX_OFFICIAL_ID = 999999;
@ -587,4 +586,20 @@ public class MTable extends X_AD_Table
return sb.toString ();
} // toString
/**
* Verify if the table contains ID=0
* @return true if table has zero ID
*/
public static boolean isZeroIDTable(String tablename) {
return (tablename.equals("AD_Org") ||
tablename.equals("AD_ReportView") ||
tablename.equals("AD_Role") ||
tablename.equals("AD_System") ||
tablename.equals("AD_User") ||
tablename.equals("C_DocType") ||
tablename.equals("GL_Category") ||
tablename.equals("M_AttributeSet") ||
tablename.equals("M_AttributeSetInstance"));
}
} // MTable

View File

@ -16,6 +16,9 @@
*****************************************************************************/
package org.compiere.model;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -33,6 +36,7 @@ import org.compiere.util.CCache;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Secure;
import org.compiere.util.SecureEngine;
/**
@ -50,7 +54,7 @@ public class MUser extends X_AD_User
/**
*
*/
private static final long serialVersionUID = -5845477151929518375L;
private static final long serialVersionUID = -5343496366428193731L;
/**
* Get active Users of BPartner
@ -167,9 +171,13 @@ public class MUser extends X_AD_User
s_log.warning ("Invalid Name/Password = " + name + "/" + password);
return null;
}
boolean hash_password = MSysConfig.getBooleanValue("USER_PASSWORD_HASH", false);
MUser retValue = null;
if (!hash_password)
{
int AD_Client_ID = Env.getAD_Client_ID(ctx);
MUser retValue = null;
String sql = "SELECT * FROM AD_User "
+ "WHERE COALESCE(LDAPUser, Name)=? " // #1
+ " AND ((Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='N') " // #2
@ -204,6 +212,40 @@ public class MUser extends X_AD_User
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
} else {
String where = " COALESCE(LDAPUser,Name) = ? AND" +
" EXISTS (SELECT * FROM AD_User_Roles ur" +
" INNER JOIN AD_Role r ON (ur.AD_Role_ID=r.AD_Role_ID)" +
" WHERE ur.AD_User_ID=AD_User.AD_User_ID AND ur.IsActive='Y' AND r.IsActive='Y') AND " +
" EXISTS (SELECT * FROM AD_Client c" +
" WHERE c.AD_Client_ID=AD_User.AD_Client_ID" +
" AND c.IsActive='Y') AND " +
" AD_User.IsActive='Y'";
MUser user = MTable.get(ctx, MUser.Table_ID).createQuery( where, null).setParameters(name).firstOnly(); // throws error if username collision occurs
String hash = null;
String salt = null;
if (user != null )
{
hash = user.getPassword();
salt = user.getSalt();
}
// always do calculation to confuse timing based attacks
if ( user == null )
user = MUser.get(ctx, 0);
if ( hash == null )
hash = "0000000000000000";
if ( salt == null )
salt = "0000000000000000";
if ( user.authenticateHash(password) )
{
retValue=user;
}
}
return retValue;
} // get
@ -308,6 +350,8 @@ public class MUser extends X_AD_User
private Boolean m_isAdministrator = null;
/** User Access Rights */
private X_AD_UserBPAccess[] m_bpAccess = null;
/** Password Hashed **/
private boolean hashed = false;
/**
@ -376,6 +420,77 @@ public class MUser extends X_AD_User
return sb.toString ();
} // cleanValue
/**
* Convert Password to SHA-512 hash with salt * 1000 iterations https://www.owasp.org/index.php/Hashing_Java
* @param password -- plain text password
*/
@Override
public void setPassword(String password) {
if ( password == null )
{
super.setPassword(password);
return;
}
boolean hash_password = MSysConfig.getBooleanValue("USER_PASSWORD_HASH", false);
if(!hash_password){
super.setPassword(password);
return;
}
if ( hashed )
return;
hashed = true; // prevents double call from beforeSave
// Uses a secure Random not a simple Random
SecureRandom random;
try {
random = SecureRandom.getInstance("SHA1PRNG");
// Salt generation 64 bits long
byte[] bSalt = new byte[8];
random.nextBytes(bSalt);
// Digest computation
String hash;
hash = SecureEngine.getSHA512Hash(1000,password,bSalt);
String sSalt = Secure.convertToHexString(bSalt);
super.setPassword(hash);
setSalt(sSalt);
} catch (NoSuchAlgorithmException e) {
super.setPassword(password);
} catch (UnsupportedEncodingException e) {
super.setPassword(password);
}
}
/**
* check if hashed password matches
*/
public boolean authenticateHash (String password) {
String hash = null;
String salt = null;
hash = getPassword();
salt = getSalt();
// always do calculation to prevent timing based attacks
if ( hash == null )
hash = "0000000000000000";
if ( salt == null )
salt = "0000000000000000";
try {
return SecureEngine.getSHA512Hash(1000, password, Secure.convertHexString(salt)).equals(hash);
} catch (NoSuchAlgorithmException ignored) {
log.log(Level.WARNING, "Password hashing not supported by JVM");
} catch (UnsupportedEncodingException ignored) {
log.log(Level.WARNING, "Password hashing not supported by JVM");
}
return false;
}
/**
* Get First Name
* @return first name
@ -801,6 +916,13 @@ public class MUser extends X_AD_User
setEMailVerifyDate(null);
if (newRecord || super.getValue() == null || is_ValueChanged("Value"))
setValue(super.getValue());
if (newRecord || is_ValueChanged("Password")) {
boolean hash_password = MSysConfig.getBooleanValue("USER_PASSWORD_HASH", false);
if (hash_password)
setPassword(getPassword());
}
return true;
} // beforeSave

View File

@ -1921,6 +1921,8 @@ public abstract class PO
continue;
return false; // one value is non-zero
}
if (MTable.isZeroIDTable(get_TableName()))
return false;
return true;
} // is_new

View File

@ -145,4 +145,6 @@ public interface SystemIDs
public final static int WINDOW_SHIPMENT_CUSTOMER = 169;
public final static int WINDOW_WAREHOUSE_LOCATOR = 139;
public final static int WINDOW_WINDOW_TAB_FIELD = 102;
public final static int SYSCONFIG_USER_HASH_PASSWORD = 200013;
}

View File

@ -665,6 +665,25 @@ public class X_AD_User extends PO implements I_AD_User, I_Persistent
return false;
}
/**
* Set Salt.
*
* @param Salt
* Random data added to improve password hash effectiveness
*/
public void setSalt(String Salt) {
set_ValueNoCheck(COLUMNNAME_Salt, Salt);
}
/**
* Get Salt.
*
* @return Random data added to improve password hash effectiveness
*/
public String getSalt() {
return (String) get_Value(COLUMNNAME_Salt);
}
public I_AD_User getSupervisor() throws RuntimeException
{
return (I_AD_User)MTable.get(getCtx(), I_AD_User.Table_Name)

View File

@ -16,6 +16,8 @@
*****************************************************************************/
package org.compiere.util;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -35,8 +37,11 @@ import org.compiere.model.MAcctSchema;
import org.compiere.model.MClientInfo;
import org.compiere.model.MCountry;
import org.compiere.model.MRole;
import org.compiere.model.MSysConfig;
import org.compiere.model.MSystem;
import org.compiere.model.MTable;
import org.compiere.model.MTree_Base;
import org.compiere.model.MUser;
import org.compiere.model.ModelValidationEngine;
@ -233,6 +238,7 @@ public class Login
private KeyNamePair[] getRoles (String app_user, String app_pwd, boolean force)
{
log.info("User=" + app_user);
long start = System.currentTimeMillis();
if (app_user == null)
{
@ -240,7 +246,7 @@ public class Login
return null;
}
// Authentification
// Authentication
boolean authenticated = false;
MSystem system = MSystem.get(m_ctx);
if (system == null)
@ -251,40 +257,130 @@ public class Login
log.warning("No Apps Password");
return null;
}
if (system.isLDAP())
{
authenticated = system.isLDAP(app_user, app_pwd);
if (authenticated)
if (authenticated){
app_pwd = null;
authenticated=true;
}
// if not authenticated, use AD_User as backup
}
boolean hash_password=MSysConfig.getBooleanValue("USER_PASSWORD_HASH", false);
KeyNamePair[] retValue = null;
ArrayList<KeyNamePair> list = new ArrayList<KeyNamePair>();
//
if(hash_password){
// adaxa-pb: try to authenticate using hashed password -- falls back to plain text/encrypted
String where = " COALESCE(LDAPUser,Name) = ? AND" +
" EXISTS (SELECT * FROM AD_User_Roles ur" +
" INNER JOIN AD_Role r ON (ur.AD_Role_ID=r.AD_Role_ID)" +
" WHERE ur.AD_User_ID=AD_User.AD_User_ID AND ur.IsActive='Y' AND r.IsActive='Y') AND " +
" EXISTS (SELECT * FROM AD_Client c" +
" WHERE c.AD_Client_ID=AD_User.AD_Client_ID" +
" AND c.IsActive='Y') AND " +
" AD_User.IsActive='Y'";
MUser user = MTable.get(m_ctx, MUser.Table_ID).createQuery( where, null).setParameters(app_user).firstOnly(); // throws error if username collision occurs
String hash = null;
String salt = null;
int AD_User_ID = -1;
if (user != null )
{
hash = user.getPassword();
salt = user.getSalt();
}
// always do calculation to confuse timing based attacks
if ( user == null )
user = MUser.get(m_ctx, 0);
if ( hash == null )
hash = "0000000000000000";
if ( salt == null )
salt = "0000000000000000";
if ( user.authenticateHash(app_pwd) )
{
authenticated = true;
AD_User_ID = user.getAD_User_ID();
app_pwd = null;
}
}
else{
StringBuffer sql = new StringBuffer("SELECT u.AD_User_ID,")
.append(" u.ConnectionProfile ")
.append(" FROM AD_User u");
sql.append(" WHERE COALESCE(u.LDAPUser,u.Name)=?");
sql.append(" AND u.IsActive='Y'").append(" AND EXISTS (SELECT * FROM AD_Client c WHERE u.AD_Client_ID=c.AD_Client_ID AND c.IsActive='Y')");
if (app_pwd != null)
sql.append(" AND ((u.Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='N') "
+ "OR (u.Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='Y'))"); // #2/3
PreparedStatement pstmt1=null;
ResultSet rs1=null;
try{
pstmt1 = DB.prepareStatement(sql.toString(), null);
pstmt1.setString(1, app_user);
if (app_pwd != null)
{
pstmt1.setString(2, app_pwd);
pstmt1.setString(3, SecureEngine.encrypt(app_pwd));
}
rs1 = pstmt1.executeQuery();
while(rs1.next()){
authenticated=true;
}
}catch (Exception ex) {
// TODO: handle exception
log.log(Level.SEVERE, sql.toString(), ex);
log.saveError("DBLogin", ex);
retValue = null;
}
finally
{
DB.close(rs1, pstmt1);
rs1 = null; pstmt1 = null;
}
}
if(authenticated){
StringBuffer sql = new StringBuffer("SELECT u.AD_User_ID, r.AD_Role_ID,r.Name,")
.append(" u.ConnectionProfile ")
.append("FROM AD_User u")
.append(" INNER JOIN AD_User_Roles ur ON (u.AD_User_ID=ur.AD_User_ID AND ur.IsActive='Y')")
.append(" INNER JOIN AD_Role r ON (ur.AD_Role_ID=r.AD_Role_ID AND r.IsActive='Y') ")
.append("WHERE COALESCE(u.LDAPUser,u.Name)=?") // #1
.append(" AND u.IsActive='Y'")
.append(" AND EXISTS (SELECT * FROM AD_Client c WHERE u.AD_Client_ID=c.AD_Client_ID AND c.IsActive='Y')");
if (app_pwd != null)
.append(" INNER JOIN AD_Role r ON (ur.AD_Role_ID=r.AD_Role_ID AND r.IsActive='Y') ");
sql.append("WHERE COALESCE(u.LDAPUser,u.Name)=?"); // #1
sql.append(" AND u.IsActive='Y'").append(" AND EXISTS (SELECT * FROM AD_Client c WHERE u.AD_Client_ID=c.AD_Client_ID AND c.IsActive='Y')");
/* if (app_pwd != null && !hash_password)
sql.append(" AND ((u.Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='N') "
+ "OR (u.Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='Y'))"); // #2/3
+ "OR (u.Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='Y'))"); // #2/3*/
sql.append(" ORDER BY r.Name");
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement(sql.toString(), null);
pstmt.setString(1, app_user);
if (app_pwd != null)
/*if (app_pwd != null && !hash_password)
{
pstmt.setString(2, app_pwd);
pstmt.setString(3, SecureEngine.encrypt(app_pwd));
}
}*/
// execute a query
rs = pstmt.executeQuery();
@ -313,7 +409,7 @@ public class Login
Env.setContext(m_ctx, "#AD_User_Name", app_user);
Env.setContext(m_ctx, "#AD_User_ID", rs.getInt(1));
Env.setContext(m_ctx, "#SalesRep_ID", rs.getInt(1));
//
if (Ini.isClient())
{
if (MSystem.isSwingRememberUserAllowed())
@ -350,7 +446,9 @@ public class Login
retValue = new KeyNamePair[list.size()];
list.toArray(retValue);
log.fine("User=" + app_user + " - roles #" + retValue.length);
}
catch (Exception ex)
{
log.log(Level.SEVERE, sql.toString(), ex);
@ -363,6 +461,7 @@ public class Login
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
}
long ms = System.currentTimeMillis () - start;
return retValue;
} // getRoles

View File

@ -16,6 +16,7 @@
*****************************************************************************/
package org.compiere.util;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
@ -378,6 +379,30 @@ public class Secure implements SecureInterface
return (convertHexString(value) != null);
} // isDigest
/**
* Convert String and salt to SHA-512 hash with iterations
* https://www.owasp.org/index.php/Hashing_Java
*
* @param value message
* @return HexString of message (length = 128 characters)
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public String getSHA512Hash (int iterations, String value, byte[] salt) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
MessageDigest digest = MessageDigest.getInstance("SHA-512");
digest.reset();
digest.update(salt);
byte[] input = digest.digest(value.getBytes("UTF-8"));
for (int i = 0; i < iterations; i++) {
digest.reset();
input = digest.digest(input);
}
digest.reset();
//
return convertToHexString(input);
} // getSHA512Hash
/**
* String Representation
* @return info

View File

@ -16,6 +16,8 @@
*****************************************************************************/
package org.compiere.util;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;
import java.util.logging.Logger;
@ -70,6 +72,22 @@ public class SecureEngine
return s_engine.implementation.getClass().getName();
} // getClassName
/**
* Convert String and salt to SHA-512 hash with iterations
* https://www.owasp.org/index.php/Hashing_Java
*
* @param value message
* @return HexString of message (length = 128 characters)
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
public static String getSHA512Hash (int iterations, String value, byte[] salt) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
if (s_engine == null)
init(System.getProperties());
return s_engine.implementation.getSHA512Hash(iterations, value, salt);
} // getDigest
/**
* Convert String to Digest.
* JavaScript version see - http://pajhome.org.uk/crypt/md5/index.html

View File

@ -16,7 +16,9 @@
*****************************************************************************/
package org.compiere.util;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
/**
@ -126,4 +128,15 @@ public interface SecureInterface
*/
public boolean isDigest (String value);
/**
* Convert String and salt to SHA-512 hash with iterations
* https://www.owasp.org/index.php/Hashing_Java
*
* @param value message
* @return HexString of message (length = 128 characters)
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public String getSHA512Hash (int iterations, String value, byte[] salt) throws NoSuchAlgorithmException, UnsupportedEncodingException;
} // SecureInterface

View File

@ -30,6 +30,8 @@ import org.compiere.model.MBPartner;
import org.compiere.model.MBPartnerLocation;
import org.compiere.model.MLocation;
import org.compiere.model.MRefList;
import org.compiere.model.MSysConfig;
import org.compiere.model.MTable;
import org.compiere.model.MUser;
/**
@ -367,6 +369,7 @@ public class WebUser
m_loc = new MLocation (m_ctx, 0, null);
//
log.info("= " + m_bp + " - " + m_bpc);
} // load
/**
@ -669,8 +672,84 @@ public class WebUser
public boolean login (String password)
{
m_loggedIn = isValid () // we have a contact
&& WebUtil.exists (password) // we have a password
&& password.equals (getPassword ());
&& WebUtil.exists (password); // we have a password
//&& password.equals (getPassword ());
boolean retValue = false;
if(m_loggedIn)
{
boolean hash_password=MSysConfig.getBooleanValue("USER_PASSWORD_HASH", false);
if(!hash_password)
{
String sql = "SELECT * FROM AD_User "
+ "WHERE COALESCE(LDAPUser, Name)=? " // #1
+ " AND ((Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='N') " // #2
+ "OR (Password=? AND (SELECT IsEncrypted FROM AD_Column WHERE AD_Column_ID=417)='Y'))" // #3
+ " AND IsActive='Y' " // #4
;
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, null);
pstmt.setString (1, m_bpc.getName());
pstmt.setString (2, password);
pstmt.setString (3, SecureEngine.encrypt(password));
rs = pstmt.executeQuery ();
if (rs.next ())
{
retValue = true;
if (rs.next())
log.warning ("More then one user with Name/Password = " + m_bpc.getName());
}
else
log.fine("No record");
}
catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
}
else{
String where = " COALESCE(LDAPUser,Name) = ? AND" +
" EXISTS (SELECT * FROM AD_User_Roles ur" +
" INNER JOIN AD_Role r ON (ur.AD_Role_ID=r.AD_Role_ID)" +
" WHERE ur.AD_User_ID=AD_User.AD_User_ID AND ur.IsActive='Y' AND r.IsActive='Y') AND " +
" EXISTS (SELECT * FROM AD_Client c" +
" WHERE c.AD_Client_ID=AD_User.AD_Client_ID" +
" AND c.IsActive='Y') AND " +
" AD_User.IsActive='Y'";
MUser user = MTable.get(m_ctx, MUser.Table_ID).createQuery( where, null).setParameters(m_bpc.getName()).firstOnly(); // throws error if username collision occurs
String hash = null;
String salt = null;
if (user != null )
{
hash = user.getPassword();
salt = user.getSalt();
}
// always do calculation to confuse timing based attacks
if ( user == null )
user = MUser.get(null, 0);
if ( hash == null )
hash = "0000000000000000";
if ( salt == null )
salt = "0000000000000000";
if ( user.authenticateHash(password) )
{
retValue=true;
}
}
}
m_loggedIn=m_loggedIn && retValue;
setPasswordOK (m_loggedIn, password);
log.fine("success=" + m_loggedIn);
if (m_loggedIn)