From 511b4a79fad701d8ef2a941bd5e927343ee7d46a Mon Sep 17 00:00:00 2001 From: rookiestyle Date: Sat, 6 Nov 2021 08:23:46 +0100 Subject: [PATCH] Update API used for two-factor authentication checks Show additional details in entry form and OTP setup form --- Translations/KeePassOTP.template.language.xml | 10 +- src/DAO/TFASites.cs | 123 ++++++---- src/KPOTP_Details.Designer.cs | 227 ++++++++++++++++++ src/KPOTP_Details.cs | 127 ++++++++++ src/KeePassOTP.csproj | 11 + src/KeePassOTPExt.cs | 15 ++ src/PluginTranslation.cs | 2 + src/Properties/AssemblyInfo.cs | 4 +- version.info | 2 +- 9 files changed, 473 insertions(+), 48 deletions(-) create mode 100644 src/KPOTP_Details.Designer.cs create mode 100644 src/KPOTP_Details.cs diff --git a/Translations/KeePassOTP.template.language.xml b/Translations/KeePassOTP.template.language.xml index 2f7f1b5..02d4824 100644 --- a/Translations/KeePassOTP.template.language.xml +++ b/Translations/KeePassOTP.template.language.xml @@ -325,6 +325,14 @@ This message will not be shown again. RecoveryCodes - Recovery Codes + Recovery codes + + + TFA_SetupURL + Setup + + + TFA_RecoveryURL + Recovery \ No newline at end of file diff --git a/src/DAO/TFASites.cs b/src/DAO/TFASites.cs index 471491b..444a27d 100644 --- a/src/DAO/TFASites.cs +++ b/src/DAO/TFASites.cs @@ -2,6 +2,7 @@ using KeePass.Resources; using KeePassLib.Serialization; using KeePassLib.Utility; +using Newtonsoft.Json.Linq; using PluginTools; using PluginTranslation; using System; @@ -14,7 +15,7 @@ namespace KeePassOTP public static class TFASites { //const string TFA_JSON_FILE = "https://twofactorauth.org/api/v2/tfa.json"; - const string TFA_JSON_FILE_DEFAULT = "https://2fa.directory/api/v2/tfa.json"; + const string TFA_JSON_FILE_DEFAULT = "https://2fa.directory/api/v3/tfa.json"; public static string TFA_JSON_FILE { @@ -31,19 +32,22 @@ public enum TFAPossible Error, } - private class TFAData + internal class TFAData { - public string name; - public string url; + public string domain; public string img; - public bool tfa; - public bool sms; - public bool email; - public bool phone; - public bool software; - public bool hardware; - public string doc; - public string category; + public string url; + public List tfa = new List(); + public string documentation; + public string recovery; + public string notes; + public string contact; + public List regions; + public List additional_domains; + public List custom_software; + public List custom_hardware; + public List keywords; + public bool tfa_possible { get { return tfa.Count > 0; } } } private enum TFALoadProcess @@ -88,7 +92,15 @@ public static string GetTFAUrl(string url) if (IsTFAPossible(url) != TFAPossible.Yes) return string.Empty; TFAData tfa; if (!m_dTFA.TryGetValue(url, out tfa)) return string.Empty; - return !string.IsNullOrEmpty(tfa.doc) ? tfa.doc : url; + return !string.IsNullOrEmpty(tfa.documentation) ? tfa.documentation : url; + } + + internal static TFAData GetTFAData(string url) + { + if (IsTFAPossible(url) != TFAPossible.Yes) return null; + TFAData tfa; + if (m_dTFA.TryGetValue(url, out tfa)) return tfa; + return null; } public static TFAPossible IsTFAPossible(string url) @@ -97,7 +109,7 @@ public static TFAPossible IsTFAPossible(string url) TFAData tfa; if (m_dTFA.TryGetValue(url, out tfa)) { - if (tfa.tfa) return TFAPossible.Yes; + if (tfa.tfa_possible) return TFAPossible.Yes; else return TFAPossible.No; } lock (m_TFAReadLock) @@ -106,7 +118,7 @@ public static TFAPossible IsTFAPossible(string url) { if (m_RetryReadOTPSites == null) { - m_RetryReadOTPSites = new System.Windows.Forms.Timer(); + m_RetryReadOTPSites = new Timer(); m_RetryReadOTPSites.Tick += OnRereadOTPSitesTimerTick; m_RetryReadOTPSites.Interval = 30 * 1000; m_RetryReadOTPSites.Enabled = false; @@ -125,11 +137,11 @@ public static TFAPossible IsTFAPossible(string url) tfa = m_dTFA.FirstOrDefault(x => IsRegexMatch(x.Key, url)).Value; if (tfa == null) { - tfa = new TFAData() { tfa = false }; + tfa = new TFAData(); m_dTFA[url] = tfa; } else m_dTFA[url] = tfa; - if (tfa.tfa) return TFAPossible.Yes; + if (tfa.tfa_possible) return TFAPossible.Yes; else return TFAPossible.No; } } @@ -142,7 +154,7 @@ private static void OnRereadOTPSitesTimerTick(object sender, EventArgs e) } } - private static System.Windows.Forms.Timer m_RetryReadOTPSites = null; + private static Timer m_RetryReadOTPSites = null; private static void TriggerReadOTPSites() { if (m_RetryReadOTPSites.Enabled) return; @@ -155,6 +167,21 @@ private static void RetryReadOTPSites(object s) m_RetryReadOTPSites.Enabled = false; } + private static string GetJSonString(JToken t, string sName) + { + string sResult = t.Value(sName); + return string.IsNullOrEmpty(sResult) ? string.Empty : sResult; + } + + private static List GetJSonList(JToken jt, string sName) + { + List lResult = new List(); + var jArrayName = jt.Value(sName); + if (jArrayName == null) return lResult; + foreach (var at in jArrayName.Children().ToList()) lResult.Add(at.ToString()); + return lResult; + } + private static void ReadOTPSites(object s) { lock (m_TFAReadLock) @@ -165,7 +192,7 @@ private static void ReadOTPSites(object s) m_LoadState = TFALoadProcess.Loading; } m_dTFA.Clear(); - JsonObject j = null; + JArray ja = null; bool bException = false; IOConnectionInfo ioc = IOConnectionInfo.FromPath(TFA_JSON_FILE); bool bExists = false; @@ -175,7 +202,12 @@ private static void ReadOTPSites(object s) { bExists = IOConnection.FileExists(ioc, true); byte[] b = IOConnection.ReadFile(ioc); - if (b != null) j = new JsonObject(new CharStream(StrUtil.Utf8.GetString(b))); + if (b != null) + { + string content = StrUtil.Utf8.GetString(b); + ja = Newtonsoft.Json.JsonConvert.DeserializeObject(content) as JArray; + + } } catch (System.Net.WebException exWeb) { @@ -190,7 +222,7 @@ private static void ReadOTPSites(object s) PluginDebug.AddError("Error reading OTP sites", 0, "Error: " + ex.Message); bException = true; } - if (j == null) + if (ja == null) { if (!bExists || bNoInternet) { @@ -199,31 +231,34 @@ private static void ReadOTPSites(object s) } else lock (m_TFAReadLock) { m_LoadState = TFALoadProcess.FileNotFound; } if (!bException) PluginDebug.AddError("Error reading OTP sites", 0); - return; + //return; } DateTime dtStart = DateTime.Now; - foreach (KeyValuePair kvp in j.Items) - { - List keys = (kvp.Value as JsonObject).Items.Keys.ToList(); - for (int i = 0; i < keys.Count; i++) - { - JsonObject k = (kvp.Value as JsonObject).GetValue(keys[i]); - TFAData tfa = new TFAData(); - var tfaDetails = k.GetValueArray("tfa"); - tfa.tfa = tfaDetails != null && tfaDetails.Length > 0; - if (!tfa.tfa) continue; - tfa.name = k.GetValue("name"); - tfa.sms = tfaDetails.Contains("sms") || k.GetValue("sms", false); - tfa.email = tfaDetails.Contains("email") || k.GetValue("email", false); - tfa.phone = tfaDetails.Contains("phone") || k.GetValue("phone", false); - tfa.software = tfaDetails.Contains("software") || k.GetValue("software", false); - tfa.hardware = tfaDetails.Contains("hardwar") || k.GetValue("hardware", false); - tfa.url = k.GetValue("url"); - tfa.img = k.GetValue("img"); - tfa.doc = k.GetValue("doc"); - tfa.category = kvp.Key; - m_dTFA[CreatePattern(tfa.url)] = tfa; - } + + foreach (JToken jtEntryContainer in ja) + { + var lEntry = jtEntryContainer.Children().ToList(); + if (lEntry.Count != 2) continue; + JToken jtEntry = lEntry[1]; + TFAData tfa = new TFAData(); + tfa.domain = GetJSonString(jtEntry, "domain"); + string sDomain = tfa.domain.ToLowerInvariant(); + if (!sDomain.StartsWith("http://") && !sDomain.StartsWith("https://")) tfa.domain = "https://" + tfa.domain; + + tfa.img = GetJSonString(jtEntry, "img"); + tfa.url = GetJSonString(jtEntry, "url"); + if (string.IsNullOrEmpty(tfa.url)) tfa.url = tfa.domain; + tfa.tfa = GetJSonList(jtEntry, "tfa"); + tfa.documentation = GetJSonString(jtEntry, "documentation"); + tfa.recovery = GetJSonString(jtEntry, "recovery"); + tfa.notes = GetJSonString(jtEntry, "notes"); + tfa.contact = GetJSonString(jtEntry, "contact"); + tfa.regions = GetJSonList(jtEntry, "regions"); + tfa.additional_domains = GetJSonList(jtEntry, "additional_domains"); + tfa.custom_software = GetJSonList(jtEntry, "custom_software"); + tfa.custom_hardware = GetJSonList(jtEntry, "custom_hardware"); + tfa.keywords = GetJSonList(jtEntry, "keywords"); + m_dTFA[CreatePattern(tfa.domain)] = tfa; } DateTime dtEnd = DateTime.Now; lock (m_TFAReadLock) diff --git a/src/KPOTP_Details.Designer.cs b/src/KPOTP_Details.Designer.cs new file mode 100644 index 0000000..d1fe611 --- /dev/null +++ b/src/KPOTP_Details.Designer.cs @@ -0,0 +1,227 @@ + +namespace KeePassOTP +{ + partial class KPOTP_Details + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tbNotes = new System.Windows.Forms.TextBox(); + this.llDocURL = new System.Windows.Forms.LinkLabel(); + this.llSetupUrl = new System.Windows.Forms.LinkLabel(); + this.lRecoveryURL = new System.Windows.Forms.Label(); + this.lNotes = new System.Windows.Forms.Label(); + this.lDocURL = new System.Windows.Forms.Label(); + this.lSetupURL = new System.Windows.Forms.Label(); + this.llRecoveryURL = new System.Windows.Forms.LinkLabel(); + this.lTFA_Modes = new System.Windows.Forms.Label(); + this.lbTFA_Modes = new System.Windows.Forms.ListBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // tbNotes + // + this.tbNotes.Dock = System.Windows.Forms.DockStyle.Fill; + this.tbNotes.Location = new System.Drawing.Point(197, 126); + this.tbNotes.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.tbNotes.Multiline = true; + this.tbNotes.Name = "tbNotes"; + this.tbNotes.ReadOnly = true; + this.tbNotes.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.tbNotes.Size = new System.Drawing.Size(640, 185); + this.tbNotes.TabIndex = 13; + // + // llDocURL + // + this.llDocURL.AutoSize = true; + this.llDocURL.Location = new System.Drawing.Point(197, 42); + this.llDocURL.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.llDocURL.Name = "llDocURL"; + this.llDocURL.Size = new System.Drawing.Size(128, 32); + this.llDocURL.TabIndex = 12; + this.llDocURL.TabStop = true; + this.llDocURL.Text = "Doc URL"; + this.llDocURL.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked); + // + // llSetupUrl + // + this.llSetupUrl.AutoSize = true; + this.llSetupUrl.Location = new System.Drawing.Point(197, 0); + this.llSetupUrl.Margin = new System.Windows.Forms.Padding(0); + this.llSetupUrl.Name = "llSetupUrl"; + this.llSetupUrl.Size = new System.Drawing.Size(153, 32); + this.llSetupUrl.TabIndex = 11; + this.llSetupUrl.TabStop = true; + this.llSetupUrl.Text = "Setup URL"; + this.llSetupUrl.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked); + // + // lRecoveryURL + // + this.lRecoveryURL.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lRecoveryURL.AutoSize = true; + this.lRecoveryURL.Location = new System.Drawing.Point(0, 84); + this.lRecoveryURL.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.lRecoveryURL.Name = "lRecoveryURL"; + this.lRecoveryURL.Size = new System.Drawing.Size(197, 32); + this.lRecoveryURL.TabIndex = 10; + this.lRecoveryURL.Text = "lRecoveryURL"; + // + // lNotes + // + this.lNotes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lNotes.AutoSize = true; + this.lNotes.Location = new System.Drawing.Point(0, 126); + this.lNotes.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.lNotes.Name = "lNotes"; + this.lNotes.Size = new System.Drawing.Size(197, 185); + this.lNotes.TabIndex = 9; + this.lNotes.Text = "Notes"; + // + // lDocURL + // + this.lDocURL.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lDocURL.AutoSize = true; + this.lDocURL.Location = new System.Drawing.Point(0, 42); + this.lDocURL.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.lDocURL.Name = "lDocURL"; + this.lDocURL.Size = new System.Drawing.Size(197, 32); + this.lDocURL.TabIndex = 8; + this.lDocURL.Text = "Doc URL"; + // + // lSetupURL + // + this.lSetupURL.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lSetupURL.AutoSize = true; + this.lSetupURL.Location = new System.Drawing.Point(0, 0); + this.lSetupURL.Margin = new System.Windows.Forms.Padding(0); + this.lSetupURL.Name = "lSetupURL"; + this.lSetupURL.Size = new System.Drawing.Size(197, 32); + this.lSetupURL.TabIndex = 7; + this.lSetupURL.Text = "Setup URL"; + // + // llRecoveryURL + // + this.llRecoveryURL.AutoSize = true; + this.llRecoveryURL.Location = new System.Drawing.Point(197, 84); + this.llRecoveryURL.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.llRecoveryURL.Name = "llRecoveryURL"; + this.llRecoveryURL.Size = new System.Drawing.Size(190, 32); + this.llRecoveryURL.TabIndex = 14; + this.llRecoveryURL.TabStop = true; + this.llRecoveryURL.Text = "RecoveryURL"; + this.llRecoveryURL.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked); + // + // lTFA_Modes + // + this.lTFA_Modes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lTFA_Modes.AutoSize = true; + this.lTFA_Modes.Location = new System.Drawing.Point(0, 321); + this.lTFA_Modes.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.lTFA_Modes.Name = "lTFA_Modes"; + this.lTFA_Modes.Size = new System.Drawing.Size(197, 243); + this.lTFA_Modes.TabIndex = 15; + this.lTFA_Modes.Text = "TFA_Modes"; + // + // lbTFA_Modes + // + this.lbTFA_Modes.Dock = System.Windows.Forms.DockStyle.Fill; + this.lbTFA_Modes.FormattingEnabled = true; + this.lbTFA_Modes.ItemHeight = 31; + this.lbTFA_Modes.Location = new System.Drawing.Point(197, 321); + this.lbTFA_Modes.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.lbTFA_Modes.Name = "lbTFA_Modes"; + this.lbTFA_Modes.Size = new System.Drawing.Size(640, 243); + this.lbTFA_Modes.TabIndex = 16; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.lSetupURL, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.lbTFA_Modes, 1, 4); + this.tableLayoutPanel1.Controls.Add(this.llSetupUrl, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.lTFA_Modes, 0, 4); + this.tableLayoutPanel1.Controls.Add(this.lDocURL, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.llRecoveryURL, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.lNotes, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.llDocURL, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.lRecoveryURL, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.tbNotes, 1, 3); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(16, 31); + this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 5; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(837, 564); + this.tableLayoutPanel1.TabIndex = 17; + // + // KPOTP_Details + // + this.AutoScaleDimensions = new System.Drawing.SizeF(16F, 31F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Transparent; + this.Controls.Add(this.tableLayoutPanel1); + this.Name = "KPOTP_Details"; + this.Padding = new System.Windows.Forms.Padding(16, 31, 16, 0); + this.Size = new System.Drawing.Size(869, 595); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TextBox tbNotes; + private System.Windows.Forms.LinkLabel llDocURL; + private System.Windows.Forms.LinkLabel llSetupUrl; + private System.Windows.Forms.Label lRecoveryURL; + private System.Windows.Forms.Label lNotes; + private System.Windows.Forms.Label lDocURL; + private System.Windows.Forms.Label lSetupURL; + private System.Windows.Forms.LinkLabel llRecoveryURL; + private System.Windows.Forms.Label lTFA_Modes; + private System.Windows.Forms.ListBox lbTFA_Modes; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + } +} diff --git a/src/KPOTP_Details.cs b/src/KPOTP_Details.cs new file mode 100644 index 0000000..83ad41e --- /dev/null +++ b/src/KPOTP_Details.cs @@ -0,0 +1,127 @@ +using KeePass.Forms; +using KeePass.Resources; +using KeePassLib; +using PluginTools; +using PluginTranslation; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace KeePassOTP +{ + public partial class KPOTP_Details : UserControl + { + private PwEntryForm _pef = null; + private PwEntry _pe = null; + private TabPage _tpKeePassOTP = null; + private TabControl _tc = null; + public KPOTP_Details() + { + InitializeComponent(); + + Dock = DockStyle.Fill; + + lSetupURL.Text = PluginTranslate.TFA_SetupURL; + lDocURL.Text = KPRes.MoreInfo; + lRecoveryURL.Text = PluginTranslate.TFA_RecoveryURL; + lNotes.Text = KPRes.Notes; + lTFA_Modes.Text = KPRes.Options; + } + + internal void InitEx(PwEntryForm f) + { + _pef = f; + _tc = Tools.GetControl("m_tabMain", _pef) as TabControl; + if (_tc == null) return; + + InitTab(); + + DoRefresh(); + } + + internal void InitEx(KeePassOTPSetup f, TFASites.TFAData tfa) + { + _tc = Tools.GetControl("tcSetup", f) as TabControl; + if (_tc == null) return; + + InitTab(); + + DoRefresh(tfa); + } + private void InitTab() + { + _tpKeePassOTP = new TabPage(_pef != null ? PluginTranslate.PluginName : KPRes.Details); + _tpKeePassOTP.Controls.Add(this); + if (_pef != null) + { + _tc.TabPages[0].Leave += CheckActiveTabStop; + _tc.TabPages.Insert(5, _tpKeePassOTP); + } + else + { + _tc.TabPages.Add(_tpKeePassOTP); + tableLayoutPanel1.RowStyles[3].SizeType = SizeType.Absolute; + tableLayoutPanel1.RowStyles[3].Height = 150; + tableLayoutPanel1.RowStyles[4].SizeType = SizeType.Absolute; + tableLayoutPanel1.RowStyles[4].Height = 150; + } + } + + private void DoRefresh() + { + _pef.UpdateEntryStrings(true, false); + var tfa = TFASites.GetTFAData(_pef.EntryStrings.ReadSafe(PwDefs.UrlField)); + DoRefresh(tfa); + } + private void DoRefresh(TFASites.TFAData tfa) + { + if (tfa == null) + { + tfa = new TFASites.TFAData(); + } + SetupUrl(lSetupURL, llSetupUrl, tfa.url); + SetupUrl(lDocURL, llDocURL, tfa.documentation); + SetupUrl(lRecoveryURL, llRecoveryURL, tfa.recovery); + tbNotes.Text = tfa.notes; + tbNotes.Enabled = !string.IsNullOrEmpty(tfa.notes); + lbTFA_Modes.Items.Clear(); + lbTFA_Modes.Items.AddRange(tfa.tfa.ToArray()); + + this.Enabled = tfa.tfa_possible; + + if (!tfa.tfa_possible) _tc.TabPages.Remove(_tpKeePassOTP); + else if (!_tc.TabPages.Contains(_tpKeePassOTP)) _tc.TabPages.Insert(5, _tpKeePassOTP); + } + + private void CheckActiveTabStop(object sender, EventArgs e) + { + DoRefresh(); + } + + private void SetupUrl(Label l, LinkLabel ll, string url) + { + ll.Text = string.Empty; + ll.Links.Clear(); + if (string.IsNullOrEmpty(url)) + { + l.Enabled = false; + return; + } + l.Enabled = true; + if (url.Length <= 150) ll.Text = url; + else ll.Text = url.Substring(0, 50) + "..." + url.Substring(url.Length - 50, 50); + ll.Links.Add(0, ll.Text.Length, url); + } + + private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + var pe = _pef == null ? _pe : _pef.EntryRef; + KeePass.Util.WinUtil.OpenUrl(e.Link.LinkData as string, pe, true); + } + } +} diff --git a/src/KeePassOTP.csproj b/src/KeePassOTP.csproj index 1f82235..6aa6d2c 100644 --- a/src/KeePassOTP.csproj +++ b/src/KeePassOTP.csproj @@ -74,6 +74,12 @@ GoogleAuthenticatorImportSelection.cs + + UserControl + + + KPOTP_Details.cs + True @@ -131,9 +137,14 @@ bin\Release\protobuf-net.dll bin\Release\zxing.dll bin\Release\zxing.presentation.dll + bin\Release\Newtonsoft.Json.dll + + 13.0.1 + + 1.0.0 diff --git a/src/KeePassOTPExt.cs b/src/KeePassOTPExt.cs index 1984a87..af0ac57 100644 --- a/src/KeePassOTPExt.cs +++ b/src/KeePassOTPExt.cs @@ -120,6 +120,7 @@ private void CleanupColumns() private void GlobalWindowManager_WindowAdded(object sender, GwmWindowEventArgs e) { + if (e.Form is PwEntryForm) e.Form.Shown += OnEntryFormShown; if (!m_bOTPHotkeyPressed) return; if (!(e.Form is AutoTypeCtxForm)) return; @@ -142,6 +143,16 @@ private void GlobalWindowManager_WindowAdded(object sender, GwmWindowEventArgs e if (lCtx.Count < 2) e.Form.Shown += OnAutoTypeFormShown; } + private void OnEntryFormShown(object sender, EventArgs e) + { + PwEntryForm pef = sender as PwEntryForm; + if (pef == null) return; + pef.Shown -= OnEntryFormShown; + + var kef = new KPOTP_Details(); + kef.InitEx(pef); + } + private void OnAutoTypeFormShown(object sender, EventArgs e) { AutoTypeCtxForm f = sender as AutoTypeCtxForm; @@ -232,6 +243,10 @@ private void OnOTPSetup(object sender, EventArgs e) otpSetup.OTP = OTPDAO.GetOTP(pe); otpSetup.EntryUrl = pe.Strings.GetSafe(PwDefs.UrlField).ReadString(); otpSetup.InitEx(); + + KPOTP_Details d = new KPOTP_Details(); + d.InitEx(otpSetup, TFASites.GetTFAData(pe.Strings.ReadSafe(PwDefs.UrlField))); + if (otpSetup.ShowDialog(m_host.MainWindow) == DialogResult.OK) OTPDAO.SaveOTP(otpSetup.OTP, pe); otpSetup.Dispose(); diff --git a/src/PluginTranslation.cs b/src/PluginTranslation.cs index bc492f4..1f4c1a0 100644 --- a/src/PluginTranslation.cs +++ b/src/PluginTranslation.cs @@ -145,6 +145,8 @@ Open it anytime to check the previous content. public static readonly string OTPRenewal_PreventShortDuration = @"Renew if soon to expire"; public static readonly string OTPDisplayMode_label = @"OTP display mode:"; public static readonly string RecoveryCodes = @"Recovery codes"; + public static readonly string TFA_SetupURL = @"Setup"; + public static readonly string TFA_RecoveryURL = @"Recovery"; #endregion #region NO changes in this area diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index 51bfa3a..340a9b9 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -30,5 +30,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.1")] -[assembly: AssemblyFileVersion("1.0.1")] +[assembly: AssemblyVersion("1.1")] +[assembly: AssemblyFileVersion("1.1")] diff --git a/version.info b/version.info index 2da347e..90e60db 100644 --- a/version.info +++ b/version.info @@ -1,5 +1,5 @@ : -KeePassOTP:1.0.1 +KeePassOTP:1.1 KeePassOTP!de:21 KeePassOTP!fr:7 KeePassOTP!pt:10