Skip to content

Commit

Permalink
Add basic support for other two factor authentications
Browse files Browse the repository at this point in the history
If a website supports other two factor authentications than OTP, you can now indicate that you use one of those.
KPOTP column will show "2FA defined*" in that case.

Use the entry's context menu for that

Close #153
  • Loading branch information
Rookiestyle committed Jun 14, 2024
1 parent 13ea569 commit a2fd7ed
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 42 deletions.
16 changes: 16 additions & 0 deletions src/DAO/OTPDAO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ public static void SaveOTP(KPOTP myOTP, PwEntry pe)
Tools.RefreshEntriesList(bModified);
}

public static void SetOtherOTPDefined(PwEntry pe, bool bDefined)
{
OTPHandler_Base h = GetOTPHandler(pe);
h.SetOtherOTPDefined(pe, bDefined);
bool bModified = (h is OTPHandler_DB) ? (h as OTPHandler_DB).DB == Program.MainForm.ActiveDatabase : true;
Tools.RefreshEntriesList(bModified);
}

public static bool IsOtherOTPDefined(PwEntry pe)
{
OTPHandler_Base h = GetOTPHandler(pe);
return h.IsOtherOTPDefined(pe);
}

internal static PwDatabase GetDB(this PwEntry pe)
{
if (pe == null) return null;
Expand Down Expand Up @@ -769,6 +783,8 @@ public class OTPHandler_Base
public virtual bool EnsureOTPUsagePossible(PwEntry pe) { return false; }
public virtual string GetReadableOTP(PwEntry pe) { return string.Empty; }
public virtual OTPDefinition OTPDefined(PwEntry pe) { return OTPDefinition.None; }
public virtual void SetOtherOTPDefined(PwEntry pe, bool bDefined) { }
public virtual bool IsOtherOTPDefined(PwEntry pe) { return false; }
public virtual KPOTP GetOTP(PwEntry pe) { return new KPOTP(); }
public virtual void SaveOTP(KPOTP myOTP, PwEntry pe) { }
public virtual void Cleanup() { }
Expand Down
60 changes: 53 additions & 7 deletions src/DAO/OTPDAO_DB.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,43 @@ public override OTPDefinition OTPDefined(PwEntry pe)
return m_peOTP.Strings.Exists(Config.OTPFIELD) ? OTPDefinition.Complete : OTPDefinition.None;
}

public override void SetOtherOTPDefined(PwEntry pe, bool bDefined)
{
if (!SetEntry(pe, false)) return;
bool bDB = StrUtil.StringToBool(m_pe.CustomData.Get(DBNAME));
if (!bDB || !OTPDB_Exists);
bool bCreated = false;
GetOTPEntry(true, out bCreated);
if (!OTPDB_Opened || (m_peOTP == null)) return;

if (bDefined == IsOtherOTPDefined(pe))return;

if (!bDefined)
{
m_peOTP.CustomData.Remove(Config.OTHEROTP);
if (!m_peOTP.Strings.Exists(Config.OTPFIELD)) m_pe.CustomData.Remove(DBNAME);
}
else
{
m_peOTP.CustomData.Set(Config.OTHEROTP, StrUtil.BoolToString(true));
m_pe.CustomData.Set(DBNAME, StrUtil.BoolToString(true));
}
Touch(m_peOTP);
m_pe.Touch(true, false);
}

public override bool IsOtherOTPDefined(PwEntry pe)
{
if (!SetEntry(pe, false)) return false;
bool bDB = StrUtil.StringToBool(m_pe.CustomData.Get(DBNAME));
if (!bDB || !OTPDB_Exists) return false;
bool bCreated = false;

GetOTPEntry(false, out bCreated);
if (!OTPDB_Opened || (m_peOTP == null)) return false;
return m_peOTP.CustomData.Exists(Config.OTHEROTP);
}

public override string GetReadableOTP(PwEntry pe)
{
if (!SetEntry(pe, false)) return string.Empty;
Expand Down Expand Up @@ -508,7 +545,9 @@ public override void SaveOTP(KPOTP myOTP, PwEntry pe)
//Create backup if something else than only the HOTP counter was changed
if (!bCreated) m_peOTP.CreateBackup(OTPDB);
}
m_peOTP.Strings.Set(Config.OTPFIELD, myOTP.OTPAuthString);
if (myOTP.OTPSeed.IsEmpty) m_peOTP.Strings.Remove(Config.OTPFIELD);
else
m_peOTP.Strings.Set(Config.OTPFIELD, myOTP.OTPAuthString);
if (myOTP.TimeCorrectionUrlOwn)
m_peOTP.CustomData.Set(Config.TIMECORRECTION, "OWNURL");
else if (string.IsNullOrEmpty(myOTP.TimeCorrectionUrl) || (myOTP.TimeCorrectionUrl == "OFF"))
Expand All @@ -521,7 +560,7 @@ public override void SaveOTP(KPOTP myOTP, PwEntry pe)
m_peOTP.Strings.Set(Config.RECOVERY, myOTP.RecoveryCodes);

Touch(m_peOTP);
if (myOTP.OTPSeed.IsEmpty)
if (myOTP.OTPSeed.IsEmpty && !IsOtherOTPDefined(m_pe))
m_pe.CustomData.Remove(DBNAME);
else
m_pe.CustomData.Set(DBNAME, StrUtil.BoolToString(true));
Expand Down Expand Up @@ -1015,25 +1054,29 @@ public int MigrateOTP2DB()
//process all entries having OTP settings
PwEntry peBackup = m_pe;
PwEntry pe_OTPBackup = m_peOTP;
foreach (PwEntry pe in DB.RootGroup.GetEntries(true).Where(x => x.Strings.Exists(Config.OTPFIELD)))
foreach (PwEntry pe in DB.RootGroup.GetEntries(true).Where(x => x.Strings.Exists(Config.OTPFIELD) || x.CustomData.Exists(Config.OTHEROTP)))
{
m_pe = pe;
ProtectedString otpfield = m_pe.Strings.GetSafe(Config.OTPFIELD);
ProtectedString recovery = m_pe.Strings.GetSafe(Config.RECOVERY);
string timecorrection = m_pe.CustomData.Get(Config.TIMECORRECTION);
if (otpfield.IsEmpty) continue;
if (otpfield.IsEmpty && !pe.CustomData.Exists(Config.OTHEROTP)) continue;
bool bCreated = false;
GetOTPEntry(true, out bCreated);
m_peOTP.Strings.Set(Config.OTPFIELD, otpfield);
if (!otpfield.IsEmpty) m_peOTP.Strings.Set(Config.OTPFIELD, otpfield);
else m_peOTP.Strings.Remove(Config.OTPFIELD);
if (!recovery.IsEmpty) m_peOTP.Strings.Set(Config.RECOVERY, recovery);
else m_peOTP.Strings.Remove(Config.RECOVERY);
if (!string.IsNullOrEmpty(timecorrection)) m_peOTP.CustomData.Set(Config.TIMECORRECTION, timecorrection);
else m_peOTP.CustomData.Remove(Config.TIMECORRECTION);
if (pe.CustomData.Exists(Config.OTHEROTP)) m_peOTP.CustomData.Set(Config.OTHEROTP, StrUtil.BoolToString(true));
else m_peOTP.CustomData.Remove(Config.OTHEROTP);
//Seed has been added to OTP db, increase moved-counter
moved++;
m_pe.Strings.Remove(Config.OTPFIELD);
m_pe.Strings.Remove(Config.RECOVERY);
m_pe.CustomData.Remove(Config.TIMECORRECTION);
m_pe.CustomData.Remove(Config.OTHEROTP);
m_pe.CustomData.Set(DBNAME, StrUtil.BoolToString(true));
m_pe.Touch(true, false);
FlagChanged(false);
Expand All @@ -1049,17 +1092,20 @@ public int MigrateOTP2Entry()
if (!Valid || !OTPDB_Exists || !OTPDB_Opened) return -1;

int moved = 0;
foreach (PwEntry peOTP in OTPDB.RootGroup.GetEntries(true).Where(x => x.Strings.Exists(Config.OTPFIELD)))
foreach (PwEntry peOTP in OTPDB.RootGroup.GetEntries(true).Where(x => x.Strings.Exists(Config.OTPFIELD) || x.CustomData.Exists(Config.OTHEROTP)))
{
PwUuid uuid = new PwUuid(MemUtil.HexStringToByteArray(peOTP.Strings.ReadSafe(UUID)));
foreach (PwEntry pe in DB.RootGroup.GetEntries(true).Where(x => uuid.Equals(x.Uuid)))
{
pe.Strings.Set(Config.OTPFIELD, peOTP.Strings.GetSafe(Config.OTPFIELD));
var psOTP = peOTP.Strings.GetSafe(Config.OTPFIELD);
if (!psOTP.IsEmpty) pe.Strings.Set(Config.OTPFIELD, psOTP);
if (peOTP.Strings.Exists(Config.RECOVERY)) pe.Strings.Set(Config.RECOVERY, peOTP.Strings.GetSafe(Config.RECOVERY));
if (peOTP.CustomData.Exists(Config.TIMECORRECTION))
pe.CustomData.Set(Config.TIMECORRECTION, peOTP.CustomData.Get(Config.TIMECORRECTION));
else
pe.CustomData.Remove(Config.TIMECORRECTION);
if (peOTP.CustomData.Exists(Config.OTHEROTP)) pe.CustomData.Set(Config.OTHEROTP, StrUtil.BoolToString(true));
else pe.CustomData.Remove(Config.OTHEROTP);
pe.CustomData.Remove(DBNAME);
pe.Touch(true, false);
moved++;
Expand Down
14 changes: 14 additions & 0 deletions src/DAO/OTPDAO_Entry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ public override OTPDefinition OTPDefined(PwEntry pe)
return pe.Strings.Exists(Config.OTPFIELD) ? OTPDefinition.Complete : OTPDefinition.None;
}

public override void SetOtherOTPDefined(PwEntry pe, bool bDefined)
{
if (bDefined == IsOtherOTPDefined(pe)) return;

if (!bDefined) pe.CustomData.Remove(Config.OTHEROTP);
if (bDefined) pe.CustomData.Set(Config.OTHEROTP, KeePassLib.Utility.StrUtil.BoolToString(true));
pe.Touch(true, false);
}

public override bool IsOtherOTPDefined(PwEntry pe)
{
return pe.CustomData.Exists(Config.OTHEROTP);
}

private static KPOTP GetSettings(PwEntry pe, bool bRecoveryCodesOnly)
{
KPOTP myOTP = new KPOTP();
Expand Down
14 changes: 12 additions & 2 deletions src/KeePassOTPColumnProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,19 @@ public override string GetCellData(string strColumnName, PwEntry pe)

if (!Config.CheckTFA) return string.Empty;
string url = pe.Strings.ReadSafe(PwDefs.UrlField);
if (string.IsNullOrEmpty(url)) return string.Empty;

TFASites.TFAPossible TFAPossible = TFASites.IsTFAPossible(url);
var bOtherOTPDefined = OTPDAO.IsOtherOTPDefined(pe);
if (string.IsNullOrEmpty(url) && !bOtherOTPDefined) return string.Empty;

if (bOtherOTPDefined && TFAPossible == TFASites.TFAPossible.Yes)
{
return PluginTranslation.PluginTranslate.TFADefined + " *";
}
else if (bOtherOTPDefined)
{
return PluginTranslation.PluginTranslate.TFADefined;
}

if (TFAPossible == TFASites.TFAPossible.Yes)
return PluginTranslation.PluginTranslate.SetupTFA;
else if (TFAPossible == TFASites.TFAPossible.Error)
Expand Down
39 changes: 36 additions & 3 deletions src/KeePassOTPExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ public partial class KeePassOTPExt : Plugin
private ToolStripMenuItem m_ContextMenuAutotype;
private ToolStripMenuItem m_ContextMenuQRCode;
private ToolStripMenuItem m_ContextMenuDelete;
private ToolStripMenuItem m_ContextMenuOtherOTPDefined;
private ToolStripMenuItem m_MainMenu;
private ToolStripMenuItem m_MainMenuCopy;
private ToolStripMenuItem m_MainMenuSetup;
private ToolStripMenuItem m_MainMenuAutotype;
private ToolStripMenuItem m_MainMenuQRCode;
private ToolStripMenuItem m_MainMenuDelete;
private ToolStripMenuItem m_MainMenuOtherOTPDefined;
private ToolStripMenuItem m_TrayMenu;
private ToolStripMenuItem m_Options;
private ToolStripMenuItem m_GoogleAuthenticatorExport;
Expand Down Expand Up @@ -225,23 +227,32 @@ private void OnEntryContextMenuOpening(object sender, EventArgs e)
SetEnabled(false, m_ContextMenuAutotype, m_MainMenuAutotype, m_ContextMenuCopy, m_ContextMenuSetup, m_MainMenuCopy, m_MainMenuSetup);
bool bEnableQR = false;
bool bDoItalic = false;
bool bOtherOTPDefined = false;
bool bComplete = false;
foreach (var pe in m_host.MainWindow.GetSelectedEntries())
{
var d = OTPDAO.OTPDefined(pe);
if (d == OTPDAO.OTPDefinition.Complete)
{
bDoItalic = false;
bEnableQR = true;
break;
bComplete = true;
//break;
}
else if (d == OTPDAO.OTPDefinition.Partial)
else if (d == OTPDAO.OTPDefinition.Partial && !bComplete)
{
bDoItalic = true;
bEnableQR = true;
}
else if (OTPDAO.IsOtherOTPDefined(pe))
{
bOtherOTPDefined = true;
}
}
SetEnabled(bEnableQR, m_ContextMenu, m_MainMenu, m_ContextMenuQRCode, m_MainMenuQRCode, m_ContextMenuDelete, m_MainMenuDelete);

m_ContextMenuOtherOTPDefined.Checked = bOtherOTPDefined;
m_MainMenuOtherOTPDefined.Checked = bOtherOTPDefined;

SetFont(m_ContextMenuSetup.Font,
m_ContextMenuCopy, m_ContextMenuAutotype, m_ContextMenuQRCode,
m_MainMenuCopy, m_MainMenuAutotype, m_MainMenuQRCode,
Expand Down Expand Up @@ -282,6 +293,8 @@ private void OnEntryContextMenuOpening(object sender, EventArgs e)
m_ContextMenuCopy, m_ContextMenuAutotype, m_ContextMenuQRCode,
m_MainMenuCopy, m_MainMenuAutotype, m_MainMenuQRCode);
}
m_ContextMenuOtherOTPDefined.Checked = OTPDAO.IsOtherOTPDefined(m_host.MainWindow.GetSelectedEntry(true));
m_MainMenuOtherOTPDefined.Checked = OTPDAO.IsOtherOTPDefined(m_host.MainWindow.GetSelectedEntry(true));
}
}

Expand Down Expand Up @@ -402,6 +415,15 @@ private void OnOTPDelete(object sender, EventArgs e)
}
}

private void OnToggleOtherOTPDefined(object sender, EventArgs e)
{
var x = sender as ToolStripMenuItem;
foreach (var pe in m_host.MainWindow.GetSelectedEntries())
{
OTPDAO.SetOtherOTPDefined(pe, x.Checked);
}
}

private void OnOTPAutotype(object sender, EventArgs e)
{
PwEntry pe = m_host.MainWindow.GetSelectedEntry(false);
Expand Down Expand Up @@ -430,10 +452,16 @@ private void CreateMenu()
m_ContextMenuSetup = new ToolStripMenuItem(PluginTranslate.OTPSetup);
m_ContextMenuSetup.Click += OnOTPSetup;
m_ContextMenuSetup.Image = Icon_Setup;
m_ContextMenuOtherOTPDefined = new ToolStripMenuItem("Other 2FA defined");
m_ContextMenuOtherOTPDefined.Click += OnToggleOtherOTPDefined;
//m_ContextMenuOtherOTPDefined.Image = Icon_Setup;
m_ContextMenuOtherOTPDefined.CheckOnClick = true;
m_ContextMenu.DropDownItems.Add(m_ContextMenuCopy);
m_ContextMenu.DropDownItems.Add(m_ContextMenuQRCode);
m_ContextMenu.DropDownItems.Add(m_ContextMenuSetup);
m_ContextMenu.DropDownItems.Add(m_ContextMenuDelete);
m_ContextMenu.DropDownItems.Add(m_ContextMenuOtherOTPDefined);

m_host.MainWindow.EntryContextMenu.Items.Insert(m_host.MainWindow.EntryContextMenu.Items.Count, m_ContextMenu);
m_ContextMenuAutotype = new ToolStripMenuItem();

Expand All @@ -451,10 +479,15 @@ private void CreateMenu()
m_MainMenuSetup = new ToolStripMenuItem(PluginTranslate.OTPSetup);
m_MainMenuSetup.Click += OnOTPSetup;
m_MainMenuSetup.Image = Icon_Setup;
m_MainMenuOtherOTPDefined = new ToolStripMenuItem("Other 2FA defined");
m_MainMenuOtherOTPDefined.Click += OnToggleOtherOTPDefined;
//m_MainMenuOtherOTPDefined.Image = Icon_Setup;
m_MainMenuOtherOTPDefined.CheckOnClick = true;
m_MainMenu.DropDownItems.Add(m_MainMenuCopy);
m_MainMenu.DropDownItems.Add(m_MainMenuQRCode);
m_MainMenu.DropDownItems.Add(m_MainMenuSetup);
m_MainMenu.DropDownItems.Add(m_MainMenuDelete);
m_MainMenu.DropDownItems.Add(m_MainMenuOtherOTPDefined);
m_MainMenuAutotype = new ToolStripMenuItem();

try
Expand Down
Loading

0 comments on commit a2fd7ed

Please sign in to comment.