IDEMPIERE-4782 Multi-factor authentication (FHCA-2034) (#955)
- support for System user with access to multiple tenants - default to re-register device when expired
This commit is contained in:
parent
9356fc1d76
commit
4e4a3d9bac
|
@ -67,10 +67,9 @@ public class MMFARegisteredDevice extends X_MFA_RegisteredDevice {
|
|||
* @return true if device is valid
|
||||
*/
|
||||
public static boolean isValid(String identifier) {
|
||||
final String where = "AD_User_ID=? AND MFADeviceIdentifier=? AND Expiration>SYSDATE";
|
||||
final String where = "AD_Client_ID IN (0,?) AND AD_User_ID=? AND MFADeviceIdentifier=? AND Expiration>SYSDATE";
|
||||
MMFARegisteredDevice rd = new Query(Env.getCtx(), Table_Name, where, null)
|
||||
.setParameters(Env.getAD_User_ID(Env.getCtx()), identifier)
|
||||
.setClient_ID()
|
||||
.setParameters(Env.getAD_Client_ID(Env.getCtx()), Env.getAD_User_ID(Env.getCtx()), identifier)
|
||||
.setOnlyActiveRecords(true)
|
||||
.first();
|
||||
return (rd != null);
|
||||
|
|
|
@ -95,7 +95,7 @@ public class MMFARegistration extends X_MFA_Registration {
|
|||
List<Object> params = new ArrayList<Object>();
|
||||
params.add(Env.getAD_User_ID(method.getCtx()));
|
||||
params.add(method.getMFA_Method_ID());
|
||||
params.add(Env.getAD_Client_ID(method.getCtx()));
|
||||
params.add(reg.getAD_Client_ID());
|
||||
params.add(reg.getMFA_Registration_ID());
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("UPDATE MFA_Registration"
|
||||
|
|
|
@ -77,6 +77,8 @@ public class EMailMechanism implements IMFAMechanism {
|
|||
|
||||
MUser user = MUser.get(ctx);
|
||||
MMFARegistration reg = new MMFARegistration(ctx, 0, trxName);
|
||||
reg.set_ValueOfColumn(MMFARegistration.COLUMNNAME_AD_Client_ID, user.getAD_Client_ID());
|
||||
reg.setAD_Org_ID(0);
|
||||
reg.setName(obfuscateEMail(prm));
|
||||
reg.setParameterValue(prm);
|
||||
reg.setMFA_Method_ID(method.getMFA_Method_ID());
|
||||
|
@ -85,7 +87,7 @@ public class EMailMechanism implements IMFAMechanism {
|
|||
reg.setIsValid(false);
|
||||
reg.setIsUserMFAPreferred(false);
|
||||
reg.setExpiration(new Timestamp(System.currentTimeMillis() + (expireMinutes * 60000)));
|
||||
reg.saveEx();
|
||||
saveRegistration(reg);
|
||||
|
||||
// send the email
|
||||
MClient client = MClient.get(ctx);
|
||||
|
@ -148,12 +150,7 @@ public class EMailMechanism implements IMFAMechanism {
|
|||
if (! valid) {
|
||||
reg.setLastFailure(new Timestamp(System.currentTimeMillis()));
|
||||
reg.setFailedLoginCount(reg.getFailedLoginCount() + 1);
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
reg.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
saveRegistration(reg);
|
||||
throw new AdempiereException(Msg.getMsg(ctx, "MFACodeInvalid"));
|
||||
}
|
||||
|
||||
|
@ -168,12 +165,7 @@ public class EMailMechanism implements IMFAMechanism {
|
|||
reg.setName(name);
|
||||
if (preferred)
|
||||
reg.setIsUserMFAPreferred(true);
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
reg.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
saveRegistration(reg);
|
||||
|
||||
return Msg.getMsg(ctx, "MFARegistrationCompleted");
|
||||
}
|
||||
|
@ -198,12 +190,7 @@ public class EMailMechanism implements IMFAMechanism {
|
|||
MUser user = MUser.get(reg.getCtx());
|
||||
reg.setMFASecret(otp);
|
||||
reg.setExpiration(new Timestamp(System.currentTimeMillis() + (expireMinutes * 60000)));
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
reg.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
saveRegistration(reg);
|
||||
|
||||
String mail_to = reg.getParameterValue();
|
||||
// send the email
|
||||
|
@ -268,7 +255,7 @@ public class EMailMechanism implements IMFAMechanism {
|
|||
if (! valid) {
|
||||
reg.setLastFailure(new Timestamp(System.currentTimeMillis()));
|
||||
reg.setFailedLoginCount(reg.getFailedLoginCount() + 1);
|
||||
reg.saveEx();
|
||||
saveRegistration(reg);
|
||||
return Msg.getMsg(ctx, "MFACodeInvalid");
|
||||
}
|
||||
|
||||
|
@ -277,14 +264,22 @@ public class EMailMechanism implements IMFAMechanism {
|
|||
reg.setFailedLoginCount(0);
|
||||
if (setPreferred)
|
||||
reg.setIsUserMFAPreferred(true);
|
||||
saveRegistration(reg);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the registration record allowing cross-tenant (saving for a user in System tenant)
|
||||
* @param reg
|
||||
*/
|
||||
private void saveRegistration(MMFARegistration reg) {
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
reg.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -109,6 +109,8 @@ public class TOTPMechanism implements IMFAMechanism {
|
|||
int expireMinutes = method.getExpireInMinutes();
|
||||
|
||||
MMFARegistration reg = new MMFARegistration(ctx, 0, trxName);
|
||||
reg.set_ValueOfColumn(MMFARegistration.COLUMNNAME_AD_Client_ID, user.getAD_Client_ID());
|
||||
reg.setAD_Org_ID(0);
|
||||
if (! Util.isEmpty(prm)) {
|
||||
reg.setName(prm);
|
||||
reg.setParameterValue(prm);
|
||||
|
@ -122,7 +124,7 @@ public class TOTPMechanism implements IMFAMechanism {
|
|||
reg.setIsUserMFAPreferred(false);
|
||||
if (expireMinutes > 0)
|
||||
reg.setExpiration(new Timestamp(System.currentTimeMillis() + (expireMinutes*60000)));
|
||||
reg.saveEx();
|
||||
saveRegistration(reg);
|
||||
|
||||
// Invalidate any other previous pending registration with same method
|
||||
MMFARegistration.invalidatePreviousPending(method, prm, reg);
|
||||
|
@ -161,7 +163,7 @@ public class TOTPMechanism implements IMFAMechanism {
|
|||
reg.setName(name);
|
||||
if (preferred)
|
||||
reg.setIsUserMFAPreferred(true);
|
||||
reg.saveEx();
|
||||
saveRegistration(reg);
|
||||
|
||||
return Msg.getMsg(ctx, "MFARegistrationCompleted");
|
||||
}
|
||||
|
@ -204,12 +206,7 @@ public class TOTPMechanism implements IMFAMechanism {
|
|||
reg.setLastFailure(new Timestamp(System.currentTimeMillis()));
|
||||
reg.setFailedLoginCount(reg.getFailedLoginCount() + 1);
|
||||
}
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
reg.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
saveRegistration(reg);
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
@ -241,15 +238,23 @@ public class TOTPMechanism implements IMFAMechanism {
|
|||
|
||||
if (setPreferred) {
|
||||
reg.setIsUserMFAPreferred(true);
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
reg.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
saveRegistration(reg);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the registration record allowing cross-tenant (saving for a user in System tenant)
|
||||
* @param reg
|
||||
*/
|
||||
private void saveRegistration(MMFARegistration reg) {
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
reg.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.compiere.model.MMFARegisteredDevice;
|
|||
import org.compiere.model.MMFARegistration;
|
||||
import org.compiere.model.MSysConfig;
|
||||
import org.compiere.model.MUser;
|
||||
import org.compiere.model.PO;
|
||||
import org.compiere.util.CLogger;
|
||||
import org.compiere.util.Env;
|
||||
import org.compiere.util.KeyNamePair;
|
||||
|
@ -82,7 +83,7 @@ public class ValidateMFAPanel extends Window implements EventListener<Event> {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 5521412080450156787L;
|
||||
private static final long serialVersionUID = -2347409338340527333L;
|
||||
|
||||
private static final CLogger logger = CLogger.getCLogger(ValidateMFAPanel.class);
|
||||
|
||||
|
@ -121,11 +122,10 @@ public class ValidateMFAPanel extends Window implements EventListener<Event> {
|
|||
this.m_orgKNPair = orgKNPair;
|
||||
this.component = this;
|
||||
|
||||
String cookieName = Env.getAD_User_ID(m_ctx) + "|" + Env.getAD_Client_ID(m_ctx);
|
||||
String registerCookie = getCookie(cookieName);
|
||||
String registerCookie = getCookie(getCookieName());
|
||||
login = new Login(ctx);
|
||||
if (login.isMFARequired(registerCookie)) {
|
||||
initComponents();
|
||||
initComponents(registerCookie != null);
|
||||
init();
|
||||
this.setId("validateMFAPanel");
|
||||
this.setSclass("login-box");
|
||||
|
@ -242,7 +242,7 @@ public class ValidateMFAPanel extends Window implements EventListener<Event> {
|
|||
this.appendChild(div);
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
private void initComponents(boolean hasCookie) {
|
||||
lblMFAMechanism = new Label();
|
||||
lblMFAMechanism.setId("lblMFAMechanism");
|
||||
lblMFAMechanism.setValue(Msg.getMsg(m_ctx, "MFALoginMechanism"));
|
||||
|
@ -288,7 +288,7 @@ public class ValidateMFAPanel extends Window implements EventListener<Event> {
|
|||
chkRegisterDevice.setId("chkRegisterDevice");
|
||||
boolean enableRegisterDevice = (daysExpire > 0);
|
||||
chkRegisterDevice.setVisible(enableRegisterDevice);
|
||||
chkRegisterDevice.setChecked(false);
|
||||
chkRegisterDevice.setChecked(hasCookie);
|
||||
|
||||
txtValidationCode = new Textbox();
|
||||
txtValidationCode.setId("txtValidationCode");
|
||||
|
@ -358,17 +358,24 @@ public class ValidateMFAPanel extends Window implements EventListener<Event> {
|
|||
}
|
||||
|
||||
if (chkRegisterDevice != null && chkRegisterDevice.isChecked()) {
|
||||
String cookieName = Env.getAD_User_ID(m_ctx) + "|" + Env.getAD_Client_ID(m_ctx);
|
||||
// TODO: generate the random cookie if possible with some fingerprint of the device
|
||||
String cookieValue = UUID.randomUUID().toString();
|
||||
setCookie(cookieName, cookieValue);
|
||||
setCookie(getCookieName(), cookieValue);
|
||||
MUser user = MUser.get(Env.getCtx());
|
||||
MMFARegisteredDevice rd = new MMFARegisteredDevice(m_ctx, 0, null);
|
||||
rd.setAD_User_ID(Env.getAD_User_ID(m_ctx));
|
||||
rd.set_ValueOfColumn(MMFARegistration.COLUMNNAME_AD_Client_ID, user.getAD_Client_ID());
|
||||
rd.setAD_Org_ID(0);
|
||||
rd.setAD_User_ID(user.getAD_User_ID());
|
||||
rd.setMFADeviceIdentifier(cookieValue);
|
||||
long daysExpire = MSysConfig.getIntValue(MSysConfig.MFA_REGISTERED_DEVICE_EXPIRATION_DAYS, 30, Env.getAD_Client_ID(m_ctx));
|
||||
rd.setExpiration(new Timestamp(System.currentTimeMillis() + (daysExpire * 86400000L)));
|
||||
// TODO: rd.setHelp -> add information about the browser, device and IP address (fingerprint)
|
||||
rd.saveEx();
|
||||
try {
|
||||
PO.setCrossTenantSafe();
|
||||
rd.saveEx();
|
||||
} finally {
|
||||
PO.clearCrossTenantSafe();
|
||||
}
|
||||
}
|
||||
|
||||
Session currSess = Executions.getCurrent().getDesktop().getSession();
|
||||
|
@ -408,6 +415,16 @@ public class ValidateMFAPanel extends Window implements EventListener<Event> {
|
|||
wndLogin.loginCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* The cookie name for the MFA registered device
|
||||
* @return
|
||||
*/
|
||||
private String getCookieName() {
|
||||
StringBuilder sb = new StringBuilder("UD_") // User Device
|
||||
.append(Env.getAD_User_ID(m_ctx));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie
|
||||
* @param name
|
||||
|
|
Loading…
Reference in New Issue