diff --git a/src/App/Pages/Vault/CipherDetailsPageViewModel.cs b/src/App/Pages/Vault/CipherDetailsPageViewModel.cs index 0c0411427..f6e7e3452 100644 --- a/src/App/Pages/Vault/CipherDetailsPageViewModel.cs +++ b/src/App/Pages/Vault/CipherDetailsPageViewModel.cs @@ -40,8 +40,8 @@ namespace Bit.App.Pages private string _totpCode; private string _totpCodeFormatted; private string _totpSec; + private double _totpInterval = Constants.TotpDefaultTimer; private bool _totpLow; - private DateTime? _totpInterval = null; private string _previousCipherId; private byte[] _attachmentData; private string _attachmentFilename; @@ -241,7 +241,7 @@ namespace Bit.App.Pages Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"]; } } - public double TotpProgress => string.IsNullOrEmpty(TotpSec) ? 0 : double.Parse(TotpSec) * 100 / 30; + public double TotpProgress => string.IsNullOrEmpty(TotpSec) ? 0 : double.Parse(TotpSec) * 100 / _totpInterval; public bool IsDeleted => Cipher.IsDeleted; public bool CanEdit => !Cipher.IsDeleted; @@ -265,6 +265,7 @@ namespace Bit.App.Pages { _totpTickHelper = new TotpHelper(Cipher); _totpTickCancellationToken?.Cancel(); + _totpInterval = _totpTickHelper.Interval; _totpTickCancellationToken = new CancellationTokenSource(); _totpTickTask = new TimerTask(_logger, StartCiphersTotpTick, _totpTickCancellationToken).RunPeriodic(); } @@ -284,6 +285,7 @@ namespace Bit.App.Pages await _totpTickHelper.GenerateNewTotpValues(); TotpSec = _totpTickHelper.TotpSec; TotpCodeFormatted = _totpTickHelper.TotpCodeFormatted; + _totpInterval = _totpTickHelper.Interval; } catch (Exception ex) { @@ -429,7 +431,6 @@ namespace Bit.App.Pages { if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null) { - _totpInterval = null; return; } _totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp); @@ -449,7 +450,6 @@ namespace Bit.App.Pages else { TotpCodeFormatted = null; - _totpInterval = null; } } diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs index 7a49ef857..45b920ace 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs @@ -22,7 +22,6 @@ namespace Bit.App.Pages private bool _websiteIconsEnabled; private string _iconImageSource = string.Empty; - public int interval { get; set; } private double _progress; private string _totpSec; private string _totpCodeFormatted; @@ -37,7 +36,6 @@ namespace Bit.App.Pages Cipher = cipherView; WebsiteIconsEnabled = websiteIconsEnabled; - interval = _totpService.GetTimeInterval(Cipher.Login.Totp); CopyCommand = new AsyncCommand(CopyToClipboardAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); diff --git a/src/App/Utilities/TotpHelper.cs b/src/App/Utilities/TotpHelper.cs index 64fc2c7a4..4d4b2e8b9 100644 --- a/src/App/Utilities/TotpHelper.cs +++ b/src/App/Utilities/TotpHelper.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; @@ -10,26 +11,26 @@ namespace Bit.App.Utilities { private ITotpService _totpService; private CipherView _cipher; - private int _interval; public TotpHelper(CipherView cipher) { _totpService = ServiceContainer.Resolve("totpService"); _cipher = cipher; - _interval = _totpService.GetTimeInterval(cipher?.Login?.Totp); + Interval = _totpService.GetTimeInterval(cipher?.Login?.Totp); } public string TotpSec { get; private set; } public string TotpCodeFormatted { get; private set; } public double Progress { get; private set; } + public double Interval { get; private set; } = Constants.TotpDefaultTimer; public async Task GenerateNewTotpValues() { var epoc = CoreHelpers.EpocUtcNow() / 1000; - var mod = epoc % _interval; - var totpSec = _interval - mod; + var mod = epoc % Interval; + var totpSec = Interval - mod; TotpSec = totpSec.ToString(); - Progress = totpSec * 100 / 30; + Progress = totpSec * 100 / Interval; if (mod == 0 || string.IsNullOrEmpty(TotpCodeFormatted)) { TotpCodeFormatted = await TotpUpdateCodeAsync(); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 99ef08614..d2cb9800f 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -33,6 +33,7 @@ public const int SelectFileRequestCode = 42; public const int SelectFilePermissionRequestCode = 43; public const int SaveFileRequestCode = 44; + public const int TotpDefaultTimer = 30; public static readonly string[] AndroidAllClearCipherCacheKeys = { diff --git a/src/Core/Services/TotpService.cs b/src/Core/Services/TotpService.cs index 358dfeb51..fe0b83649 100644 --- a/src/Core/Services/TotpService.cs +++ b/src/Core/Services/TotpService.cs @@ -24,7 +24,7 @@ namespace Bit.Core.Services { return null; } - var period = 30; + var period = Constants.TotpDefaultTimer; var alg = CryptoHashAlgorithm.Sha1; var digits = 6; var keyB32 = key; @@ -117,7 +117,7 @@ namespace Bit.Core.Services public int GetTimeInterval(string key) { - var period = 30; + var period = Constants.TotpDefaultTimer; if (key != null && key.ToLowerInvariant().StartsWith("otpauth://")) { var qsParams = CoreHelpers.GetQueryParams(key); diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index bb3d5586b..027b7a757 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Web; using Bit.Core.Models.Domain; +using Bit.Core.Services; using Newtonsoft.Json; namespace Bit.Core.Utilities @@ -182,26 +184,20 @@ namespace Bit.Core.Utilities public static Dictionary GetQueryParams(string urlString) { - var dict = new Dictionary(); - if (!Uri.TryCreate(urlString, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Query)) + try { - return dict; + if (!Uri.TryCreate(urlString, UriKind.Absolute, out var uri) || string.IsNullOrWhiteSpace(uri.Query)) + { + return new Dictionary(); + } + var queryStringNameValueCollection = HttpUtility.ParseQueryString(uri.Query); + return queryStringNameValueCollection.AllKeys.Where(k => k != null).ToDictionary(k => k, k => queryStringNameValueCollection[k]); } - var pairs = uri.Query.Substring(1).Split('&'); - foreach (var pair in pairs) + catch (Exception ex) { - var parts = pair.Split('='); - if (parts.Length < 1) - { - continue; - } - var key = System.Net.WebUtility.UrlDecode(parts[0]).ToLower(); - if (!dict.ContainsKey(key)) - { - dict.Add(key, parts[1] == null ? string.Empty : System.Net.WebUtility.UrlDecode(parts[1])); - } + LoggerHelper.LogEvenIfCantBeResolved(ex); } - return dict; + return new Dictionary(); } public static string SerializeJson(object obj, bool ignoreNulls = false)